I have a template 'Foo', which owns a T, and I'd like it to have a variadic constructor that forwards its arguments to T's constructor:
template<typename T>
struct Foo {
Foo()
: t() {}
Foo(const Foo& other)
: t(other.t) {}
template<typename ...Args>
Foo(Args&&... args)
: t(std::forward<Args>(args)...) {}
T t;
};
However, this causes Foo to not be copyable:
int main(int argc, char* argv[]) {
Foo<std::shared_ptr<int>> x(new int(42));
decltype(x) copy_of_x(x); // FAILS TO COMPILE
return EXIT_SUCCESS;
}
because, according to this answer, the non-constness of the argument causes the variadic constructor to be a better match. I don't want to force my callers to use const_cast, for obvious reasons.
One possible solution I found was to write a "copy constructor" for Foo which takes a non-const Foo and uses constructor forwarding:
Foo(Foo& other)
: Foo(const_cast<const Foo&>(other)) {}
When this constructor is defined, things work again: now the non-const Foo argument copy ctor is preferred. However, this seems very sketchy to me, in that this "cure" seems worse than the disease.
Is there another way to achieve this effect, to indicate that the natural copy constructor should be preferred to the variadic constructor? If not, are there any adverse consequences of defining this non-const argument copy constructor?
You can use some ugly SFINAE with std::enable_if
, but I'm not sure it is better than your initial solution (in fact, I'm pretty sure it's worse!):
#include <memory>
#include <type_traits>
// helper that was not included in C++11
template<bool B, typename T = void> using disable_if = std::enable_if<!B, T>;
template<typename T>
struct Foo {
Foo() = default;
Foo(const Foo &) = default;
template<typename Arg, typename ...Args, typename = typename
disable_if<
sizeof...(Args) == 0 &&
std::is_same<typename
std::remove_reference<Arg>::type,
Foo
>::value
>::type
>
Foo(Arg&& arg, Args&&... args)
: t(std::forward<Arg>(arg), std::forward<Args>(args)...) {}
T t;
};
int main(int argc, char* argv[]) {
Foo<std::shared_ptr<int>> x(new int(42));
decltype(x) copy_of_x(x);
decltype(x) copy_of_temp(Foo<std::shared_ptr<int>>(new int));
return 0;
}
The best approach is to not do what you're doing.
That said, a simple fix is to let the variadic constructor forward up to a base class constructor, with some special first argument.
E.g. the following compiles with MinGW g++ 4.7.1:
#include <iostream> // std::wcout, std::endl
#include <memory> // std::shared_ptr
#include <stdlib.h> // EXIT_SUCCESS
#include <tuple>
#include <utility> // std::forward
void say( char const* const s ) { std::wcout << s << std::endl; }
template<typename T>
struct Foo;
namespace detail {
template<typename T>
struct Foo_Base
{
enum Variadic { variadic };
Foo_Base()
: t()
{ say( "default-init" ); }
Foo_Base( Foo_Base const& other )
: t( other.t )
{ say( "copy-init" ); }
template<typename ...Args>
Foo_Base( Variadic, Args&&... args )
: t( std::forward<Args>(args)... )
{ say( "variadic-init" ); }
T t;
};
template<typename T>
struct Foo_ConstructorDispatch
: public Foo_Base<T>
{
Foo_ConstructorDispatch()
: Foo_Base<T>()
{}
template<typename ...Args>
Foo_ConstructorDispatch( std::tuple<Foo<T>&>*, Args&&... args )
: Foo_Base<T>( args... )
{}
template<typename ...Args>
Foo_ConstructorDispatch( std::tuple<Foo<T> const&>*, Args&&... args )
: Foo_Base<T>( args... )
{}
template<typename ...Args>
Foo_ConstructorDispatch( void*, Args&&... args)
: Foo_Base<T>( Foo_Base<T>::variadic, std::forward<Args>(args)... )
{}
};
} // namespace detail
template<typename T>
struct Foo
: public detail::Foo_ConstructorDispatch<T>
{
template<typename ...Args>
Foo( Args&&... args)
: detail::Foo_ConstructorDispatch<T>(
(std::tuple<Args...>*)0,
std::forward<Args>(args)...
)
{}
};
int main()
{
Foo<std::shared_ptr<int>> x( new int( 42 ) );
decltype(x) copy_of_x( x );
}
If not, are there any adverse consequences of defining this non-const argument copy constructor?
I am going to ignore the "If not", since there are other approaches. But there is an adverse consequence of your approach. The following still uses the template constructor
Foo<X> g();
Foo<X> f(g());
Because g()
is an rvalue, the template is a better match because it deduces the parameter to an rvalue reference.
Disable the constructor when the argument type is the same type as or derived from this:
template<typename ThisType, typename ... Args>
struct is_this_or_derived : public std::false_type {};
template<typename ThisType, typename T>
struct is_this_or_derived<ThisType, T>
: public std::is_base_of<std::decay_t<ThisType>, std::decay_t<T> >::type {};
template<typename ThisType, typename ... Args>
using disable_for_this_and_derived
= std::enable_if_t<!is_this_or_derived<ThisType, Args ...>::value>;
Use it as
template<typename ...Args
, typename = disable_for_this_and_derived<Foo, Args ...> >
//^^^^
//this needs to be adjusted for each class
Foo(Args&&... args) : t(std::forward<Args>(args)...) {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With