1

I'm trying to inherit from a bunch of classes of which I know an interface, but however they may have very different constructors. To do so I decided to use variadic templates into the derived class constructor, so that it may get the arbitrary parameters that will in the end feed the parent class.

My code is the following:

#include <iostream>

struct A {
    A(int) { std::cout << "This is int\n"; }
    A(unsigned) { std::cout << "This is unsigned\n"; }
};

struct B {
    B(char) { std::cout << "This is char\n"; }
};

template <typename T>
struct C : public T {
    template <typename... Args>
    C(double, Args&&... params) : T(std::forward<Args>(params)...) { std::cout << "This is double\n"; }
    // But what about this?
    // C(Args&&... params, double) : T(std::forward<Args>(params)...) { std::cout << "This is double\n"; }
};

int main() {
    C<A> p(1.0, 1);
    C<A> r(1.0, 1u);
    C<B> q(1.0, 'c');

    // Which would work as following
    // C<A> p(1,   1.0);
    // C<A> r(1u,  1.0);
    // C<B> q('c', 1.0);
    return 0;
}

My questions are:

  • Is this code correct? This is my first attempt with variadic templates in constructors, so I'd love to hear your opinion if I missed something.
  • I'd prefer to leave the parameters of the child class C at the end, however as far as I understand this is not possible since in a constructor you are not allowed to specify the template parameters, and in that case the variadic arguments would swallow all the parameters, leaving none for the actual child. Is there a way to do this by any chance?
Constructor
  • 7,273
  • 2
  • 24
  • 66
Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • My gut feeling is that this is isn't the best way to solve this problem. Can't you just accept the right amount of arguments and do something like this: http://stackoverflow.com/questions/120876/c-superclass-constructor-calling-rules. – maxywb May 08 '14 at 18:50
  • @maxywb: But he cannot know how many arguments is the right number of arguments, because he derives from the template argument. – Deduplicator May 08 '14 at 18:53

1 Answers1

2

14.8.2.1 Deducing template arguments from a function call

1 [...] For a function parameter pack that does not occur at the end of the parameter-declaration-list, the type of the parameter pack is a non-deduced context.

5 [...] [ Note: If a template-parameter is not used in any of the function parameters of a function template, or is used only in a non-deduced context, its corresponding template-argument cannot be deduced from a function call and the template-argument must be explicitly specified. — end note ]

14.8.2.5 Deducing template arguments from a type

If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

So, as you observe, Args... should be explicitly specified, but since this is not possible for a constructor, this is not going to work.

What can work is to pack all arguments meant for the base constructor in a tuple (live example), followed by the remaining arguments for the derived class:

template <typename T>
class C : public T
{
    template<typename... A, size_t... I>
    C(std::tuple<A...>&& a, sizes<I...>, double x) :
        T(std::get<I>(std::move(a))...) { std::cout << "This is double\n"; }

public:
    template<typename... A>
    C(std::tuple<A...>&& a, double x) :
        C(std::move(a), idx<sizeof...(A)>{}, x) { }
};

to be used as

int main() {
    C<A> p(std::forward_as_tuple(1),      1.0);
    C<A> r(std::forward_as_tuple(1u, 2.), 1.0);
    C<B> q(std::forward_as_tuple('c', 0), 1.0);
}

where I have made your example a bit richer:

struct A {
    A(int) { std::cout << "This is int\n"; }
    A(unsigned, double) { std::cout << "This is unsigned, double\n"; }
};

struct B {
    B(char, int) { std::cout << "This is char, int\n"; }
};

and the remaining boilerplate

template<size_t... I> struct sizes { using type = sizes<I...>; };

template<size_t N, size_t K = 0, size_t... I>
struct idx_t : idx_t<N, K+1, I..., K> {};

template<size_t N, size_t... I>
struct idx_t<N, N, I...> : sizes<I...> {};

template<size_t N>
using idx = typename idx_t<N>::type;

is there just until std::integer_sequence is available.

If you find std::forward_as_tuple too long a name, it's not hard to define you own e.g. pack:

template<typename... A>
constexpr std::tuple<A&&...>
pack(A&&... a) { return std::tuple<A&&...>{std::forward<A>(a)...}; }

Even so, syntax

C<B> q(pack('c', 0), 1.0);

does add a little overhead, but I find it natural that the end user has a hint of what is going on. Flattening to C<B> q('c', 0, 1.0); would not only be nearly impossible to implement if more parameters are added to the derived class constructor, but also ambiguous and confusing to the user. What would be really cool is a syntax like

C<B> q('c', 0; 1.0);

(also e.g. to separate input/output parameters in functions), but I haven't seen this in any language, only in textbooks.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53