Why variadic template constructor matches better than copy constructor?

前端 未结 3 1058
灰色年华
灰色年华 2021-01-11 20:49

The following code does not compile:

#include 
#include 

struct Foo
{
    Foo() { std::cout << \"Foo()\" << std::         


        
相关标签:
3条回答
  • 2021-01-11 21:13

    This call:

    Bar<Foo> bar2{bar1};
    

    has two candidates in its overload set:

    Bar(const Bar&);
    Bar(Bar&);       // Args... = {Bar&}
    

    One of the ways to determine if one conversion sequence is better than the other is, from [over.ics.rank]:

    Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

    — [...]
    — S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example:

    int f(const int &);
    int f(int &);
    int g(const int &);
    int g(int);
    
    int i;
    int j = f(i);    // calls f(int &)
    int k = g(i);    // ambiguous
    

    —end example ]

    The forwarding reference variadic constructor is a better match because its reference binding (Bar&) is less cv-qualified than the copy constructor's reference binding (const Bar&).

    As far as solutions, you could simply exclude from the candidate set anytime Args... is something that you should call the copy or move constructor with SFINAE:

    template <typename... > struct typelist;
    
    template <typename... Args,
              typename = std::enable_if_t<
                  !std::is_same<typelist<Bar>,
                                typelist<std::decay_t<Args>...>>::value
              >>
    Bar(Args&&... args)
    

    If Args... is one of Bar, Bar&, Bar&&, const Bar&, then typelist<decay_t<Args>...> will be typelist<Bar> - and that's a case we want to exclude. Any other set of Args... will be allowed just fine.

    0 讨论(0)
  • 2021-01-11 21:22

    While I agree that it's counter-intuitive, the reason is that your copy constructor takes a const Bar& but bar1 is not const.

    http://coliru.stacked-crooked.com/a/2622b4871d6407da

    Since the universal reference can bind anything it is chosen over the more restrictive constructor with the const requirement.

    0 讨论(0)
  • 2021-01-11 21:28

    Another way to avoid the variadic constructor being selected is to supply all forms of the Bar constructor.

    It's a little more work, but avoids the complexity of enable_if, if that's important to you:

    #include <iostream>
    #include <utility>
    
    struct Foo
    {
        Foo() { std::cout << "Foo()" << std::endl; }
        Foo(int) { std::cout << "Foo(int)" << std::endl; }
    };
    
    template <typename T>
    struct Bar
    {
        Foo foo;
    
        Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
        Bar(Bar&) { std::cout << "Bar(Bar&)" << std::endl; }
        Bar(Bar&&) { std::cout << "Bar(Bar&&)" << std::endl; }
    
        template <typename... Args>
        Bar(Args&&... args) : foo(std::forward<Args>(args)...)
        {
            std::cout << "Bar(Args&&... args)" << std::endl;
        }
    };
    
    int main()
    {
        Bar<Foo> bar1{};
        Bar<Foo> bar2{bar1};
    }
    
    0 讨论(0)
提交回复
热议问题