2015-08-29 37 views
5

如何声明复制构造函数,如果我们有通用引用参数的构造函数,也?C++ 11构造函数与可变参数通用引用和复制构造函数

http://coliru.stacked-crooked.com/a/4e0355d60297db57

struct Record{ 
    template<class ...Refs> 
    explicit Record(Refs&&... refs){ 
     cout << "param ctr" << endl; 
    } 

    Record(const Record& other){  // never called 
     cout << "copy ctr" << endl; 
    } 

    Record(Record&& other){   // never called 
     cout << "move ctr" << endl; 
    }  
}; 

int main() { 
    Record rec("Hello");  
    Record rec2(rec); // do "param ctr" 

    return 0; 
} 

根据这个构造器的表std::tuplehttp://en.cppreference.com/w/cpp/utility/tuple/tuple [看看情况3,8]这个问题在标准库某种程度上解决了......但我无法通过STL的代码获得。


P.S.有些问题与C++ universal reference in constructor and return value optimization (rvo)有关

P.P.S.就目前而言,我只是增加了第一个参数Record(call_constructor, Refs&&... refs)以用于真正的EXPLICIT调用。我可以手动检测,如果我们只有一个参数,如果它是Record,并重定向呼叫复制ctr /参数ctr,但....我不能相信有没有这种标准的方式...

+2

我不能写一个完整的答案,但这应该可以帮助你https://akrzemi1.wordpress.com/2013/10/10/too-perfect-forwarding/基本的想法是使用SFINAE。 –

+0

@DanielJour这有点类似于我的P.附:部分,但thx,我很高兴知道这是已知的问题... – tower120

+0

为了将来证明这个问题,应该指出*通用引用*将正式称为*转发引用*。该提案可以在[这里]找到(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf)。 – huu

回答

2

在您的示例中,转发引用与Record&一起使用。

所以,你可以添加额外的过载Record&(转发到拷贝构造函数):

Record(Record& other) : Record(static_cast<const Record&>(other)) {} 

或在一个与转发参考使用SFINAE。

+0

是否有任何缺点(使用手册'const_cast')?那么移动ctr呢? – tower120

+0

@ tower120:删除'const'可能是危险的,我添加'const'时看到的唯一副作用是使用其他过载(如这里)。 – Jarod42

+0

@ tower120:移动构造函数可能已经被调用,但如果需要的话,您可以为'const Record &&'添加额外的重载。 – Jarod42

1

在转发引用上超载是一种糟糕的做法(请参阅有效的现代C++,第26项)。由于超负荷解决规则,他们往往吞噬你传给他们的一切。

在你的例子中,你正在构造一个非const的对象Record对象,这就是为什么你的副本没有得到执行。如果你称它为this

Record rec2(const_cast<Record const&>(rec)); 

然后它按预期工作。

一个解决方案是在构造函数上使用转发参考和禁用应该调用copy ctor的情况;它变得有点丑陋到一个可变的情况下,虽然写:

template < 
    class Ref1, class ...Refs, 
    typename = typename std::enable_if < 
     !std::is_same<Ref1, Record&>::value || sizeof...(Refs) 
    >::type 
> 
explicit Record(Ref1&& ref, Refs&&... refs) 
{ 
    cout << "param ctr" << endl; 
} 

现在呼吁

Record rec2(rec); // calls copy ctor 

分派给拷贝构造函数,因为模板不能被实例化Record&

Demo


如果你发现自己做这个有很多(不推荐),你可以通过定义类型特点做SFINAE去除一些杂波

template<class T1, class T2, class... Refs> 
using no_copy_ctor = typename std::enable_if < 
    !std::is_same<T1, T2>::value || sizeof...(Refs)>::type; 

这样写上面

template<class Ref1, class ...Refs, typename = no_copy_ctor<Record&, Ref1, Refs...>> 
explicit Record(Ref1&& ref, Refs&&... refs) 
{ /*...*/ } 
+0

你必须(重新) - 用你的SFINAE解决方案实现默认的构造函数。 – Jarod42

+0

而条件实际上更复杂,你忘记检查'sizeof ...(Refs)== 0' – Jarod42

+0

@ Jarod42你假设OP想使用可变参数构造函数作为零参数的构造函数。 –

1

的问题

当你打电话给Record rec2(rec);时,你有两个可行的构造函数:你的拷贝构造函数Record(Record const&)和可变参数构造函数Refs = {Record&},其计算结果为Record(Record&)。后者是一个更好的候选人,因为它是一个不合格的参考,所以即使这不是你想要的,它也会赢。

解决方案

要删除任何应该拨打移动或正在为可变参数构造一个可行的候选拷贝构造函数。用简单的英语,如果Refs...由一个类型的引用或者一个普通的值组成,这个类型来自Record - 我们不想使用可变参数构造函数。包括派生的情况下,同样是很重要的,因为你当然希望SpecialRecord sr; Record r(sr);调用拷贝构造函数...

因为这个上来,这是非常有用的作为一种特质。基本情况是,它既不是复制或移动:

template <typename T, typename... Ts> 
struct is_copy_or_move : std::false_type { }; 

我们只需要专注于单一类型:

template <typename T, typename U> 
struct is_copy_or_move<T, U> 
: std::is_base_of<T, std::decay_t<U>> 
{ } 

然后,我们只是有这个SFINAE”来代替我们的可变参数的构造函数d选择:

template <typename... Refs, 
      typename = std::enable_if_t<!is_copy_or_move<Record, Refs...>::value> 
      > 
Record(Refs&&...); 

现在,如果参数是这样的,这应该是调用拷贝或移动的构造,一个可变利弊结构工将不再可行。