8

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.)

Michael
  • 7,407
  • 8
  • 41
  • 84
  • 7
    `concept Duckable`... coming to you in a [TS](http://wg21.link/p0121) near you. – Kerrek SB Apr 27 '16 at 12:54
  • 2
    Well it is not standard yet but hopefully C++17 will allow us to use [`auto`](http://en.cppreference.com/w/cpp/language/auto) as a function parameter to make this easier. – NathanOliver Apr 27 '16 at 12:54
  • @KerrekSB It's not on the docket for C++17, right? Maybe we'll see it in 2020. :( – erip Apr 27 '16 at 13:01
  • @erip: You can ask your vendor whether they implement the TS. I think GCC and Clang do, or will do soon. – Kerrek SB Apr 27 '16 at 13:03
  • @KerrekSB I meant as a part of the standard. I'm trying to write more and more standard-compliant code. :) – erip Apr 27 '16 at 13:05
  • @NathanOliver Writing auto in a normal function is part of the concepts TS. Also: the concept TS will make it into the standard eventually and it is already implemented in GCC 6. So unless you need C++17 compliant code you could probably use it. – Klemens Morgenstern Apr 27 '16 at 13:06
  • @KlemensMorgenstern Which is why I said it is not standard yet. I try not to suggest not standard methods unless the OP specifically asks for it. – NathanOliver Apr 27 '16 at 13:08
  • @NathanOliver: OP is an adult, so there's no reason to hide non-standard code ;-) Answers containing non-standard-code are perfectly fine, as long as you clearly mention the non-standard-status. As OP you should be able to decide by yourself whether you use it or not. – Michael Apr 29 '16 at 11:30

5 Answers5

11

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.

lisyarus
  • 15,025
  • 3
  • 43
  • 68
5

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

101010
  • 41,839
  • 11
  • 94
  • 168
1

#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)...);
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 1
    I agree with Kerrek and I furthermore think that the solution does neither clarify anything nor is it easier to read/write... – Michael Apr 27 '16 at 13:10
1

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.

marcinj
  • 48,511
  • 9
  • 79
  • 100
0

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());
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142