3

Suppose I have a base class that might later be "extended" by deriving from it, let's call this class Base, and the extension Derived. The template signature of the classes is fixed, and cannot be altered (ie. we cannot change the template arguments to the classes). The writer of the Derived class knows nothing about Base, only that it might take some arguments to its constructor.

However, the caller of the final derived class knows how many arguments should be passed. How can I write this Derived extension? Here is what I have:

struct Base
{
    Base(int baseArg) {}
};

struct Derived : public Base
{
    template <typename... Args>
    Derived(Args&&... args, int derivedArg)
    :
        Base(std::forward<Args>(args)...)
    {
    }
};

When I try to run this with Derived d(1, 1); I get the following erorr message:

prog.cpp: In function 'int main()':
prog.cpp:19:16: error: no matching function for call to 'Derived::Derived(int, int)'
  Derived d(1, 1);
                ^
prog.cpp:19:16: note: candidates are:
prog.cpp:11:2: note: template<class ... Args> Derived::Derived(Args&& ..., int)
  Derived(Args&&... args, int myArg)
  ^
prog.cpp:11:2: note:   template argument deduction/substitution failed:
prog.cpp:19:16: note:   candidate expects 1 argument, 2 provided
  Derived d(1, 1);
                ^
prog.cpp:8:8: note: constexpr Derived::Derived(const Derived&)
 struct Derived : public Base
        ^
prog.cpp:8:8: note:   candidate expects 1 argument, 2 provided
prog.cpp:8:8: note: constexpr Derived::Derived(Derived&&)
prog.cpp:8:8: note:   candidate expects 1 argument, 2 provided

The constructor to Derived should take 2 arguments, using the first to construct itself and passing the 2nd to the base class. Why doesn't this work?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
quant
  • 21,507
  • 32
  • 115
  • 211

2 Answers2

6

nth gets the nth element of some parameters:

template<size_t n, class...Args>
auto nth( Args&&... args )
->typename std::tuple_element<n,std::tuple<Args&&...>>::type
{
  return std::get<n>( std::forward_as_tuple(std::forward<Args>(args)...) );
}

This uses the above to extract the last, and all-but-last, argument and send them to the appropriate spot:

struct Derived : public Base {
  struct tag{};
  template <typename... Args>
  Derived(Args&&... args) : Derived(
    tag{},
    std::make_index_sequence<sizeof...(Args)-1>{},
    std::forward<Args>(args)...
  ){}
  template<size_t...Is, class...Args>
  Derived(tag, std::index_sequence<Is...>, Args&&...args ):
    Base(nth<Is>(std::forward<Args>(args)...)...)
  {
    int derivedArg = nth<sizeof...(Args)-1>(std::forward<Args>(args)...);
  }
};

We build a sequence for the first n-1 elements, pass them to base, and store the last element for ourselves.

It is far easier if you put the extra argument first, however.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • The C++14 version of `nth` should probably use `decltype(auto)`, no? – T.C. Feb 03 '15 at 04:14
  • @t.c. yep: but made C++11 period using tuple element. – Yakk - Adam Nevraumont Feb 03 '15 at 11:03
  • What is `tag` for? (you also need a `...` after the first `std::forward`) – David G Feb 07 '15 at 01:35
  • @0x499602D2 `Derived` has a perfect forwarding ctor. `tag` is a type that won't be in the pack of arguments so I can forward to a 2nd ctor that behaves differently. Index sequence is not enough, because `Base` could have a ctor that takes an index sequence as the first argument. – Yakk - Adam Nevraumont Feb 07 '15 at 02:06
  • `std::get( std::forward_as_tuple(std::forward(args)...) );` - genius! I'm using this to intercept forwarded arguments and do something different with `<0>` in the derived class (after forwarding to the base). Sorcery! – underscore_d Jan 12 '16 at 18:50
  • @Yakk-AdamNevraumont - If possible, can you post the complete C++14 version of `nth` as a separate section in your answer? – TCSGrad Feb 09 '19 at 04:50
  • @tscgrad The C++11 one is also C++14. I mean, I would remove the `->` trailing return clause, replace `auto` with `decltype(auto)`, but only for brevity if this was originally a 14 question. – Yakk - Adam Nevraumont Feb 09 '19 at 08:06
4

The Derived constructor is a non-deduced context. From [temp.deduct.type], §14.8.2.5/5:

The non-deduced contexts are:

— [ .. ]

— A function parameter pack that does not occur at the end of the parameter-declaration-list.

In your case, the parameter pack isn't at the end - there's one more argument after it, so it's a non-deduced context, which makes your program ill-formed. The simple solution is to just invert the ordering:

template <typename... Args>
Derived(int derivedArg, Args&&... args) // totally OK
:
    Base(std::forward<Args>(args)...)
{
}
Barry
  • 286,269
  • 29
  • 621
  • 977