0

I'm looking to create an inheritance-based tuple, similar to how it was done in https://stackoverflow.com/a/52208842/1284735 except with constructors.

I think my regular constructor seems to work fine but when I attempt to use the copy constructor, the compiler says that I'm not providing any argument to the function. What am I missing?

tuple.cpp:41:5: note: candidate constructor not viable: requires single argument 't', but no arguments were provided TupleImpl(TupleImpl& t):

template<size_t Idx, typename T> struct TupleLeaf { T val; TupleLeaf() = default; // I think should also have proper constructors but left like this for simplicity TupleLeaf(T t): val(t) {} }; template<size_t Idx, typename... Args> struct TupleImpl; template<size_t Idx, typename T, typename... Args> struct TupleImpl<Idx, T, Args...>: TupleLeaf<Idx, T>, TupleImpl<Idx + 1, Args...> { template<typename F, typename... Rest> TupleImpl(F&& val, Rest&&... args): TupleLeaf<Idx, T>(std::forward<F>(val)), TupleImpl<Idx + 1, Args...>(std::forward<Rest>(args)...) {} TupleImpl(TupleImpl& t): TupleLeaf<Idx, T>(static_cast<TupleLeaf<Idx, T>>(t).val), TupleImpl<Idx + 1, Args...>(t) {} }; template<size_t Idx> struct TupleImpl<Idx> { TupleImpl() = default; TupleImpl(TupleImpl<Idx> &t) {} }; template<typename... Args> using Tuple = TupleImpl<0, Args...>; int main() { Tuple<int, char, string> tup{1, 'a', "5"}; // Works okay Tuple<int, char, string> x = tup; // Fails here } 
3
  • 1
    Your code works if you remove TupleImpl(TupleImpl& t) Commented Apr 10, 2024 at 21:59
  • Yea, you are right. It works for me if I remove either constructor. Why are they interfering with each other? Commented Apr 10, 2024 at 22:37
  • I think I somewhat know the answer. Casting t to be an lvalue and match the constructor param type worked. TupleImpl<Idx + 1, Args...>(static_cast<TupleImpl<Idx + 1, Args...>&>(t)). The first constructor seems to be matching after the copy constructor call otherwise Commented Apr 10, 2024 at 23:18

1 Answer 1

3

Tuple<int, char, std::string> is an alias for TupleImpl<0, int, char, std::string> which has two constructors after deduction:

  1. TupleImpl(TupleImpl&) which is a specialization of the first constructor with F equal to TupleImpl& and Rest empty.
  2. TupleImpl(TupleImpl&) from the second constructor.

Constructor #2 is selected because non-template functions are preferred over template specializations. (For what it's worth, this is not the typical copy constructor signature.)

This constructor calls its base TupleImpl<1, char, std::string>'s constructor with a reference to the same argument (which has the type of the derived class).

After deduction, there are again two constructors:

  1. TupleImpl(Tuple&) which is a specialization of the first constructor with F equal to Tuple& and Rest empty.
  2. TupleImpl(TupleImpl&)

Since the argument is of type Tuple which is derived from TupleImpl in this context, the first is a better match.

This constructor then calls its base Tuple<2, std::string>'s constructor with the forwarded Rest arguments, which is an empty parameter pack. It tries to default construct Tuple<2, std::string> but it does not have a default constructor.

This is the meaning of the error: both constructors of Tuple<2, std::string> require one argument and we pass zero.

You can fix this by using static_cast to perform the derived-to-base conversion. However, the template constructor is still too greedy. Even if you use static_cast, the template constructor needs to be constrained in order to not take over the TupleImpl(const TupleImpl&) signature (or the reverse, if you use the more typical copy constructor signature). You can solve both by constraining the template constructor:

template<typename F, typename... Rest> requires (!std::derived_from<std::decay_t<F>, TupleImpl>) TupleImpl(F&& val, Rest&&... args): TupleLeaf<Idx, T>(std::forward<F>(val)), TupleImpl<Idx + 1, Args...>(std::forward<Rest>(args)...) {} 

https://godbolt.org/z/5bxMbM5Yc

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.