具有两个参数的decltype是什么?

57

编辑,为了避免混淆: decltype 不接受两个参数。请参见答案。

以下两个结构体可用于在编译时检查类型 T 上是否存在成员函数:

// Non-templated helper struct:
struct _test_has_foo {
    template<class T>
    static auto test(T* p) -> decltype(p->foo(), std::true_type());

    template<class>
    static auto test(...) -> std::false_type;
};

// Templated actual struct:
template<class T>
struct has_foo : decltype(_test_has_foo::test<T>(0))
{};

我认为这个想法是在检查成员函数的存在时使用SFINAE,因此如果p->foo()无效,则仅定义返回std::false_type的省略号版本的test。否则,第一种方法针对T*进行定义,并将返回std::true_type。实际的“开关”发生在第二个类中,该类继承自test返回的类型。与使用is_same等不同的方法相比,这似乎很聪明且“轻量级”。
对于带有两个参数的decltype,我起初感到惊讶,因为我认为它只是获取表达式的类型。当我看到上面的代码时,我认为它就像“尝试编译表达式并始终返回第二个类型。如果表达式无法编译,则失败”(因此隐藏此特化; SFINAE)。
但是:
然后我想我可以使用这种方法编写任何“有效表达式”检查器,只要它依赖于某种类型T。例如:
...
    template<class T>
    static auto test(T* p) -> decltype(bar(*p), std::true_type());
...

http://ideone.com/dJkLPF

我原本以为这样会返回一个std::true_type,只有当bar接受T作为第一个参数时才会返回(或者如果T可转换等),也就是说:如果在某个上下文中定义了类型为T*p,那么bar(*p)将编译通过。

然而,进行上述修改后,结果总是评估为std::false_type。为什么呢?我不想用一些复杂的不同代码来修复它。我只想知道为什么它不能按照我的预期工作。显然,带有两个参数的decltype与我想象的不同。我找不到任何文档;它只在每个地方用一个表达式解释。


10
这是“逗号运算符”(comma operator)。 - Praetorian
6
@Praetorian: 哦天啊。我对这个问题感到困惑,现在看了你的评论我才明白原因。这太可怕了。而且很棒。 - Mooing Duck
1
@Praetorian 这太简单了。如果你只使用别人写的代码,就会发生这种情况... ;) - leemes
2个回答

56

这是一个用逗号分隔的表达式列表,其类型与列表中最后一个表达式的类型相同。通常用于验证第一个表达式是否有效(可编译,类似SFINAE),第二个表达式用于指定如果第一个表达式有效时decltype应该返回什么类型。


2
好的,但是为什么它不接受 bar(*p) 如果 bar 接受一个 T - leemes
@leemes 在你提供的例子中,bar 并不接受所有的 T,它只接受 std::ostream。因此,如果 *p 返回 std::istream,那么 bar(*p) 就会失败,就像第二次调用一样。 - Daniel Frey
1
@leemes,这是因为std::ostream不可复制。也许这个例子应该是int bar(const std::ostream&) {return 0;}?如果你把它改成这样,它就可以工作了。 - Daniel Frey
好的,我现在有一个不同的例子,它存在问题(我的实际问题):我想测试 QVariant::fromValue(t) 的有效性,更确切地说,是否会编译通过(而不仅仅是其签名)。在这里,这很重要,因为它是一个使用类型来查询更多信息的模板函数,这可能会失败(在函数本身内部!)。decltype 似乎对此没有问题。在这种情况下,它总是将检查器定义为 true。使用这种方法似乎不可能编写“表达式将编译”检查,而只能编写“具有正确的签名/类型以使其有效”。 - leemes
@leemes 一个 XY 问题 :) 我建议您提出一个新问题,并附上一个包含您真实问题的 SSCCE,因为我不确定评论是否足以解释它。 - Daniel Frey
显示剩余3条评论

20

decltype不接受两个参数。简单来说,它可以有一个表达式作为其参数,并且逗号运算符是创建表达式的一种方式。根据第5.18/1段:

[...]由逗号分隔的一对表达式从左到右进行求值;左边的表达式是废弃值表达式(第5条)。与左表达式相关的每个值计算和副作用都在与右表达式相关的每个值计算和副作用之前顺序排列。结果的类型和值是右操作数的类型和值;结果与其右操作数具有相同的值类别,并且如果右操作数是glvalue和位域,则结果是位域。如果右操作数的值是临时值(12.2),则结果是该临时值。

因此:

static_assert(std::is_same<decltype(42, 3.14), double>::value, "Will not fire");

好的,这解释了decltype的作用。但是你能解释一下为什么我的第二个检查器总是返回std::false_type吗?也就是说,decltype的东西无法编译(至少这是我在ideone代码中看到的结果,这是我的解释)? - leemes
1
@leemes:抱歉,你的评论在披萨送到前半秒钟就掉了 :) 基本上,Daniel Frey在他最后一条回答中解释了它。 - Andy Prowl
@AndyProwl,你能帮我理解你的static_assert代码吗?逗号运算符不会导致无论std::is_same<decltype(42, 3.14), double>::value的结果如何,都始终返回“Will not fire”吗? - Jonathan Mee
1
@JonathanMee 不对。static_assert 需要两个参数。 - milleniumbug
1
@JonathanMee:那不是逗号运算符。static_assert 接受两个参数:一个布尔表达式和一个字符串字面量。如果布尔表达式求值为 false,编译时断言会触发,并且字符串字面量的内容将显示在错误消息中。否则,不会触发编译时失败。 - Andy Prowl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接