2014-10-01 19 views
15

香草萨特的回到基础! CppCon的现代C++演示文稿讨论了传递参数的不同选项,并比较了它们的性能与易于编写/教学。 “高级”选项(在所有情况下提供最佳的性能测试,但太难对于大多数开发者编写)是完美转发,给定(PDF, pg. 28)的例子:完美转发设置器上的'enable_if`约束是什么?

class employee { 
    std::string name_; 

public: 
    template <class String, 
       class = std::enable_if_t<!std::is_same<std::decay_t<String>, 
                std::string>::value>> 
    void set_name(String &&name) noexcept(
     std::is_nothrow_assignable<std::string &, String>::value) { 
     name_ = std::forward<String>(name); 
    } 
}; 

的示例使用转发的模板函数参考,其中使用enable_if约束了模板参数String。但是这个约束似乎是不正确的:似乎是说这种方法只有在String类型不是std::string时才可以使用,这是没有意义的。这意味着std::string成员可以使用设置,除std::string之外的任何值。

using namespace std::string_literals; 

employee e; 
e.set_name("Bob"s); // error 

一种解释我考虑的是,有一个简单的错字和约束的用意是std::is_same<std::decay_t<String>, std::string>::value而不是!std::is_same<std::decay_t<String>, std::string>::value。然而,这意味着制定者不适用于(例如)const char *,并且显然它打算使用这种类型,因为这是在演示文稿中测试的案例之一。

在我看来,正确的约束更像是:

允许任何可以被分配给成员与二传手使用。

我得到了正确的约束吗?还有其他可以改进的地方吗?对原始约束是否有任何解释,或许它是脱离了语境?


另外我想知道这个声明中复杂的,“不可测试”的部分是否真的有益。因为我们没有使用过载,我们可以简单地依靠正常的模板实例:

template <class String> 
void set_name(String &&name) noexcept(
    std::is_nothrow_assignable<decltype((name_)), String>::value) { 
    name_ = std::forward<String>(name); 
} 

当然还有的在noexcept是否真正重要的一些争论,有的说不要担心太多关于它除了移动/交换原语:

template <class String> 
void set_name(String &&name) { 
    name_ = std::forward<String>(name); 
} 

也许有了概念,限制模板并不是不合理的困难,只是为了改进错误信息。

template <class String> 
    requires std::is_assignable<decltype((name_)), String>::value 
void set_name(String &&name) { 
    name_ = std::forward<String>(name); 
} 

这仍然会对它不能是虚拟的缺点,它必须在头(虽然希望模块将最终渲染没有实际意义),但这似乎相当受教。

+0

嗯,房间里有人说它的约束不正确,不接受字符串文字 - 也许我们可以cc @HowardHinnant?它也不适合我。 [视频的谈话](http://youtu.be/xnqTKD8uD64?t=1h15m21s) – dyp 2014-10-01 19:26:59

+0

@PiotrS .: MSVC没有别名,所以我忘了:/ – 2014-10-01 21:10:34

+0

@MooingDuck嘿,VS2013支持别名和他们的stdlib工具C++ 14 type_trait别名。 – bames53 2014-10-01 21:17:21

回答

3

我认为你所拥有的可能是正确的,但为了不写一个简单的“我同意”的答案,我会提出这个建议,而不是根据正确的类型检查分配 - 无论是lval中,RVAL,常量,不管:

template <class String> 
auto set_name(String&& name) 
-> decltype(name_ = std::forward<String>(name), void()) { 
    name_ = std::forward<String>(name); 
} 
+0

当你添加'noexcept'子句时,它会变得有些丑陋。 – 2015-04-12 17:55:58

4

一种解释我考虑的是,有一个简单的错字和约束的用意是std::is_same<std::decay_t<String>, std::string>::value而不是!std::is_same<std::decay_t<String>, std::string>::value

是的,这是呈现在屏幕上的权利限制是is_same,不!is_same。看起来你的幻灯片上有一个错字。


但是这将意味着制定者不与,例如工作,const char *

是的,我相信这是故意的。当文字等"foo"一个字符串传递给接受通用参考一个函数,那么推导出的类型不是指针(因为数组衰减到由值在模板参数捕获仅当指针),更确切地说,它是一个const char(&)[N]。这就是说,每到set_name调用与字符串字面不同长度的将实例化一个新的set_name专业化,如:

void set_name(const char (&name)[4]); // set_name("foo"); 
void set_name(const char (&name)[5]); // set_name("foof"); 
void set_name(const char (&name)[7]); // set_name("foofoo"); 

约束应该定义通用参考,使其接受并推导出只要么std::string类型对于左值参数,或cv-std::string&用于左值参数(这就是为什么它在std::is_same条件下与std::string比较之前为std::decay)。


它显然是打算用这种类型考虑到这是在展示测试的情况下一个工作。

我认为测试版(4)没有限制(注意它被命名为String&& +完美转发),所以它可能是那样简单:

template <typename String> 
void set_name(String&& name) 
{ 
    _name = std::forward<String>(name); 
} 

,这样,当一个字符串字面它在函数调用之前并不构造std::string实例,就像非模板化版本会这样做(不必要地在被调用者的代码中分配内存只是为了构建一个std::string临时文件,最终将被移动到可能的预分配目标,如_name):

void set_name(const std::string& name); 
void set_name(std::string&& name); 

在我看来,正确的约束更像是:​​

没有,因为我写的,我不认为目的是为了约束set_name接受类型可分配std::string。再一次 - 这原来std::enable_if是那里有一个单一的实现set_name功能采取的通用参考只接受std::string的右值和左值(仅此而已,虽然这是一个模板)。在您的std::enable_if版本中,传递任何不可分配给std::string的东西都会产生错误,无论试图这样做时是否存在常量。请注意,我们最终可能只是移动该name参数_name如果是非const右值引用,因此检查可转让的时候,我们不使用SFINAE排除有利于其他超负荷的过载分辨率功能,除了从的情况下毫无意义。


什么就完美了转发二传手正确enable_if约束?

template <class String, 
      class = std::enable_if_t<std::is_same<std::decay_t<String>, 
               std::string>::value>> 

或根本没有约束可言,如果它不会造成任何性能损失(就像传递字符串文字)。

+0

*“我不认为其意图是将set_name限制为接受可分配给'std :: string'的类型”*如果我们将它与其他解决方案进行比较,它应该至少可以隐式转换为'std :: string '以允许与其他解决方案相同的转换。但是这看起来不合适,因为我们不希望在分配之前执行该隐式转换。 – dyp 2014-10-01 21:22:04

+0

@dyp:这就是为什么类型仅限于'std :: string's,而不是类型* assignable *到字符串的一个很好的理由,那是什么意思? – 2014-10-01 21:36:37

+0

嗯,我的意思是其他的解决方案允许'x.set_name(“hello”);',而这个不 - '“hello”'不是'std :: string',但它可以隐式转换为'std :: string &&'和'std :: string const&'和'std :: string'。 – dyp 2014-10-01 21:42:32

1

我想在一个评论,以适应这些想法,但是他们将不适合。正如我在上面的评论和赫伯的伟大的演讲中提到的那样,我想这样写。

对不起,是迟到了。我已经审查了我的笔记,事实上我对向Herb建议选项4的原始约束减去错误的!感到内疚。没有人是完美的,因为我妻子肯定会证实,至少我所有。 :-)

提醒:香草的点(我同意)是开始与简单的C++ 98/03的建议

set_name(const string& name); 

和仅在需要时有移动。选项#4有相当的动作。如果我们正在考虑选项#4,我们只能在应用程序的关键部分对负载,存储和分配进行计数。我们需要尽可能快地将name分配给name_。如果我们在这里,代码可读性远不如性能重要,尽管正确性仍然是重要的。

根本没有提供约束(对于选项#4),我认为有些不正确。如果一些其他的代码试图与它是否能够调用employee::set_name来约束自己,就可能得到错误的答案,如果set_name完全不受限:

template <class String> 
auto foo(employee& e, String&& name) 
-> decltype(e.set_name(std::forward<String>(name)), void()) { 
    e.set_name(std::forward<String>(name)); 
    // ... 

如果set_name不受约束且String推导出一些完全无关的类型X,对foo的上述限制在过载集合中不正确地包括foo的此实例化。正确性仍然是国王...

如果我们想要分配一个字符到name_怎么办?说A。应该允许吗?它应该是快速的邪恶?

e.set_name('A'); 

那么,为什么不呢?! std::string便是这样一个赋值操作符:

basic_string& operator=(value_type c); 

但是请注意,有没有相应的构造函数:

basic_string(value_type c); // This does not exist 

因此is_convertible<char, string>{}false,但is_assignable<string, char>{}true

这是一个逻辑错误尝试设置与char一个string的名字(除非你想文档添加到employee说是这么说的)。因此,即使原来的C++ 98/03的实施并没有让语法:

e.set_name('A'); 

它确实允许在一个不太有效的方式相同的逻辑操作:

e.set_name(std::string(1, 'A')); 

而且我们正在处理选项#4,因为我们迫切希望尽可能地优化这个东西。

由于这些原因,我认为is_assignable是限制此功能的最佳特征。风格上我发现Barry's technique for spelling this constraint quite acceptable。因此这是我投票的地方。

另请注意,employeestd::string这里只是Herb的演讲中的例子。他们是处理在你的代码的替代品。此建议旨在推广到您必须处理的代码。

+0

这对于需要一个约束来防止将一个int分配给'name_'很好,但是在我看来,这是一个特殊情况,因为应用了从int到'char'的隐式转换。理想情况下,字符串类可以在这种情况下禁止隐式转换,但我认为至少我们可以在'employee :: set_name'中执行某些操作。那么[这](http://coliru.stacked-crooked.com/a/b25e0861fbeb7c00)?或者,也许使用大括号这是太微妙,因此就像SFINAE约束一样无法达到? – bames53 2015-04-12 18:16:52

+0

@ bames53:在我的例子中,我的错误是使用'int'作为例子“other type”。我应该使用'class rut​​abaga'并演示'rutabaga'没有转换为'std :: string'。然而,有时候'员工'吃'大头菜',所以可以想象'foo'在'员工'和'大头菜'上超载,或者'大头菜'隐式地转换为'蔬菜'或模板化的东西代表“蔬菜”的参数。即使用扩展的SFINAE,*任何*表达式都可以用作约束,甚至可以使用'e.set_name'。 – 2015-04-12 20:45:02

+0

但是,如果用'is_assignable'约束,'String = int'的'foo'将仍然在重载集中,因为'int'(和'double'等)可以转换为'char'。 – 2015-04-12 22:14:28