Forwarding All Constructors in C++0X

Forwarding all constructors in C++0x

There is a better way in C++0x for this

class X: public Super {
using Super::Super;
};

If you declare a perfect-forwarding template, your type will behave badly in overload resolution. Imagine your base class is convertible from int and there exist two functions to print out classes

class Base {
public:
Base(int n);
};

class Specific: public Base {
public:
template<typename... Args>
Specific(Args&&... args);
};

void printOut(Specific const& b);
void printOut(std::string const& s);

You call it with

printOut("hello");

What will be called? It's ambiguous, because Specific can convert any argument, including character arrays. It does so without regard of existing base class constructors. Inheriting constructors with using declarations only declare the constructors that are needed to make this work.

C++0x perfect forwarding getting in the way of copy ctor?

Yes, this is the correct behaviour.

There are two constructors that might be called in this case - your copy constructor and the template one. However, template constructor might deduce the type to be perfect match (Wrapper(StringT&&) with StringT = Wrapper& yields perfect match Wrapper(Wrapper&)) and thus gets used instead of copy constructor.

Suggested workaround - use std::enable_if from <type_traits>:

template <typename StringT>
Wrapper(StringT&& value,
typename std::enable_if<
!std::is_same<
StringT,
Wrapper&
>::value
>::type* = 0)
: value(std::forward<StringT>(value))
{ }

See it working here.

How useful would Inheriting Constructors be in C++?

2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?

I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in C++0x .

Also, the perfect forwarding approach can't "forward" the expliciteness of base-class constructors: Either it is always a converting constructor or never, and the base-class will always be direct initialized (always making use of all constructors, even explicit ones).

Another problem are initializer-list constructors because you can't deduce Args to initializer_list<U>. Instead, you would need to forward to the base with B{args...} (note the braces) and initialize D objects with (a, b, c) or {1, 2, 3} or = {1, 2, 3}. In that case, Args would be the element types of the initializer list, and forward them to the base class. A initializer-list constructor can then receive them. This seems to cause unnecessary code bloat because the template argument pack will potentially contain lots of type sequences for each different combination of types and length and because you have to choose an initialization syntax this means:

struct MyList {
// initializes by initializer list
MyList(std::initializer_list<Data> list);

// initializes with size copies of def
MyList(std::size_t size, Data def = Data());
};

MyList m{3, 1}; // data: [3, 1]
MyList m(3, 1); // data: [1, 1, 1]

// either you use { args ... } and support initializer lists or
// you use (args...) and won't
struct MyDerivedList : MyList {
template<class ... Args>
MyDerivedList(Args&& ... args) : MyList{ args... } { }
};

MyDerivedList m{3, 1}; // data: [3, 1]
MyDerivedList m(3, 1); // data: [3, 1] (!!)

Passing/Moving parameters of a constructor in C++0x

You take each one by value, like this:

struct foo
{
foo(std::string s, bar b, qux q) :
mS(std::move(s)),
mB(std::move(b)),
mQ(std::move(q))
{}

std::string mS;
bar mB;
qux mQ;
};

The initialization of the function parameters by the argument will either be a copy-constructor or move-constructor. From there, you just move the function parameter values into your member variables.

Remember: copy- and move-semantics are a service provided by the class, not by you. In C++0x, you no longer need to worry about how to get your own "copy" of the data; just ask for it and let the class do it:

foo f("temporary string is never copied", bar(), quz()); // no copies, only moves
foo ff(f.mS, f.mB, f.mQ); // copies needed, will copy
foo fff("another temp", f.mB, f.mQ); // move string, copy others

Note: your constructor only takes in values, those values will figure out how to construct themselves. From there, of course, it's up to you to move them where you want them.

This applies everywhere. Have a function that needs a copy? Make it in the parameter list:

void mutates_copy(std::string s)
{
s[0] = 'A'; // modify copy
}

mutates_copy("no copies, only moves!");

std::string myValue = "don't modify me";
mutates_copy(myValue); // makes copy as needed
mutates_copy(std::move(myValue)); // move it, i'm done with it

In C++03, you could emulate it fairly well, but it wasn't common (in my experience):

struct foo
{
foo(std::string s, bar b, qux q)
// have to pay for default construction
{
using std::swap; // swaps should be cheap in any sane program

swap(s, mS); // this is effectively what
swap(b, mB); // move-constructors do now,
swap(q, mQ); // so a reasonable emulation
}

std::string mS;
bar mB;
qux mQ;
};

c++0x inherited constructor in templates

template <typename T>
struct bar: public foo<T>
{
using foo<T>::foo<T>;
};

To let this parse correctly, you would need to insert template before the foo<T>;, to tell the compiler that foo is to be regarded as a template name (it cannot look into foo<T> to tell itself, since T is unknown). But using ::template is not allowed in a using declaration. The name also does not refer to all constructors of bar: Instead, it would refer to a specific constructor function template specialization (T is the template argument) of such a constructor as follows

template<typename T>
foo();

In addition, it's not valid for a using declaration to use a template-id (like foo<T>) as its name (which in effect forbids it to refer to function template specialization, with the addition of forbidding to name conversion function template specializations stated too), so even if you correct the parsing problem using ::template (if it would be possible), you would still error out at this point.

When inherited constructors were introduced, special rules were added that allow to reference a constructor using a syntactic rule: If you have a qualified-id (which basically a qualified name using ...::...), and the last qualified before the final part names a specific class, then you can denote the constructor(s) of that class in two additional ways:

  • If the class was named using a template-id (a name of the form foo<T>) and the final part matches the template name (so, foo<T>::foo or TTP<T>::TTP with TTP being a template template parameter).
  • If the final part matches the class name (so, foo::foo or T::T, with T being a template parameter).

These two additional rules are only active in a using declaration. And they were naturally not present in C++03. The other rule that was also present in C++03 is: If the final part names the injected class name, then this qualified name also refers to the constructor:

  • foo::foo would therefor work. But with this rule alone, T::T (where T denotes class foo) would not work, because foo has no member called T.

Therefor, with the special rules in place you can write

using foo<T>::foo;
using bar::foo::foo; // valid too

The second is valid too: foo is the injected class name which was injected into the base class foo<T> and inherited to bar. We refer to that name by bar::foo, and then add the last part foo, which refers to the injected class name again, to denote the constructor(s) of `foo.

Now you understand why the initial name you tried would refer to a constructor function template specialization (if it were to be allowed to): Because the foo<T>::foo part would name all constructors, and the <T> that would follow would then filter out the template and pass the type argument.

Why is forwarding reference constructor called instead of copy constructor?

I am guessing that it's because the move constructor was implicitly defined but not the copy constructor.

No, both are implicitly defined for class Something.

Why is the copy constructor being resolved to the templated forwarding reference constructor

Because the copy constructor takes const Something& as its parameter. That means for the copy constructor to be called, implicit conversion is needed for adding const qualifier. But the forwarding reference constructor could be instantiated to take Something& as its parameter, then it's an exact match and wins in the overload resolution.

So if you make something const, the implicitly defined copy constructor will be invoked for the 3rd case instead of the forwarding reference constructor.

LIVE

but not the move constructor?

Because for the move constructor the above issue doesn't matter. For the invocation of the 1st and 2nd case, both implicitly defined move constructor and forwarding reference constructor are exact match, then non-template move constructor wins.

rvalue references and constructor arguments

On the contrary, C++11 makes it easier thanks to universal references:

template <typename T> struct Wrapper
{
T value;

template <typename U> Wrapper(U && u)
: value(std::forward<U>(u))
{ }
};

As an extra nice touch, you should add a defaulted second argument that only exists when T is constructible from U, so as to not make your class itself appear constructible from unmatching types. And make it variadic, too:

template <typename ...Args>
Wrapper(Args &&... args,
typename std::enable_if<std::is_constructible<T, Args...>::value, int>::type = 0)
: value(std::forward<Args>(args)...)
{ }

Make sure to #include <utility> for forward and #include <type_traits> for the traits.



Related Topics



Leave a reply



Submit