Static duck typing in C++

眉间皱痕 提交于 2021-02-07 02:39:56

问题


C++ has some sort of duck typing for types given by template parameters. We have no idea what type DUCK1 and DUCK2 will be, but as long as they can quack(), it will compile and run:

template <class DUCK1, class DUCK2>
void let_them_quack(DUCK1* donald, DUCK2* daisy){
  donald->quack();
  daisy->quack();
}

But it's a bit inconvenient to write. When I do absolutely not care what actual types DUCK1 and DUCK2 are but rather want to fully use the idea of duck typing, then I would like to have something sligthly different than above:

  1. I'd like to omit writing a template parameter list that is repetitive and mostly meaningless (Just imagine what would happen if there are 7 ducks...)
  2. I'd like to make it a bit more explicit that the types are never used and that it's only the interface that matters.
  3. I'd like to have sort of an interface annotation/check. Make somehow clear what interface is expected behind the type. (That's, however, a bit in contrast of duck typing.)

Does C++ offer any features to achieve one or more of the 3 ideas?

(I know that virtual inheritance is the method of choice in most cases to implement such patterns, but the question here is specifically about the case of static polymorphism.)


回答1:


Concerning questions 1 and 2: since C++14 you can omit explicit template <typename ... boilerplate and use auto, but only in lambdas:

auto let_them_quack = [] (auto & donald, auto & daisy){
    donald.quack();
    daisy.quack();
};

(yes, I prefer references to pointers). GCC allows to do so in usual functions as an extension.

For the question 3, what you are talking about are called concepts. They existed in C++ for a long time, but only as a documentational term. Now the Concepts TS is in progress, allowing you to write something like

template<typename T>
concept bool Quackable = requires(T a) {
    a.quack();
};

void let_them_quack (Quackable & donald, Quackable & daisy);

Note that it is not yet C++, only a technical specification in progress. GCC 6.1 already seems to support it, though. Implementations of concepts and constraints using current C++ are possible; you can find one in boost.




回答2:


I'd like to omit writing a template parameter list that is repetitive and mostly meaningless (Just imagine what would happen if there are 7 ducks...)

For that you could use variadic templates and do something like the following:

template<typename DUCK>
void let_them_quack(DUCK &&d) {
  d.quack();
}

template<typename DUCK, typename... Args>
void let_them_quack(DUCK &&d, Args&& ...args) {
  d.quack();
  let_them_quack(std::forward<Args>(args)...);
}

Live Demo




回答3:


#2 and #3 are sort of taken care of by the fact that the code will not compile, and throw a compilation error, if the given classes don't implement the interface. You could also make this formal:

class duck {

public:
   virtual void quack()=0;
};

Then declare the parameters to the function as taking a pointer to a duck. Your classes will have to inherit from this class, making the requirements for let_them_quack() crystal clear.

As far as #1 goes, variadic templates can take care of this.

void let_them_quack()
{
}

template <typename ...Args>
void let_them_quack(duck* first_duck, Args && ...args) {
  first_duck->quack();
  let_them_quack(std::forward<Args>(args)...);
}



回答4:


You will be able to make it look preetier with concept (not yet in standard - but very close):

http://melpon.org/wandbox/permlink/Vjy2U6BPbsTuSK3u

#include <iostream>

template<typename T>concept bool ItQuacks(){
    return requires (T a) {
        { a.quack() } -> void;
    };
}

void let_them_quack2(ItQuacks* donald, ItQuacks* daisy){
  donald->quack();
  daisy->quack();
}

struct DisneyDuck {
    void quack(){ std::cout << "Quack!";}
};

struct RegularDuck {
    void quack(){ std::cout << "Quack2!";}
};

struct Wolf {
    void woof(){ std::cout << "Woof!";}
};

int main() {
    DisneyDuck q1, q2;
    let_them_quack2(&q1, &q2);

    RegularDuck q3, q4;
    let_them_quack2(&q3, &q4);    

    //Wolf w1, w2;
    //let_them_quack2(&w1, &w2);    // ERROR: constraints not satisfied
}

output:

 Quack!Quack!Quack2!Quack2!

As you can see, you will be able to: omit writing a template parameter list, ItQuacks is quite explicit so types are never used and that it's only the interface that matters takes place. This I'd like to have sort of an interface annotation/check. also takes place, concept use will also give you meaningfull error message.




回答5:


We only need to write one version of the function:

#include <utility>

template<typename... Quackers>
void let_them_quack(Quackers&& ...quackers) {
  using expand = int[];

  void(expand { 0, (std::forward<Quackers>(quackers).quack(), 0)... });
}

struct Duck {
  void quack() {}
};

int main()
{
  Duck a, b, c;
  let_them_quack(a, b, c, Duck());
}


来源:https://stackoverflow.com/questions/36890357/static-duck-typing-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!