How can I prevent a variadic constructor from being preferred to the copy constructor?

前端 未结 4 1810
生来不讨喜
生来不讨喜 2020-12-13 14:09

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

        
相关标签:
4条回答
  • 2020-12-13 14:16

    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;
    }
    
    0 讨论(0)
  • 2020-12-13 14:22

    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 );
    }
    
    0 讨论(0)
  • 2020-12-13 14:31

    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.

    0 讨论(0)
  • 2020-12-13 14:37

    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)...) {}
    
    0 讨论(0)
提交回复
热议问题