decltype从表达式列表推导类型错误的原因及方案确认
问题分析与解答
我在测试一段类型可迭代性检测的代码时,编写了如下C++代码:
#include <iostream> #include <typeinfo> namespace Test { struct MyPtr { double operator, (int Input) { std::cout << "Operator, called" << std::endl; return 1.0; } MyPtr operator* () { std::cout << "operator* called" << std::endl; return *this ; } MyPtr operator++ () { std::cout << "operator++ called" << std::endl; return *this; } MyPtr operator!= (const MyPtr& Other) { std::cout << "operator!= called" << std::endl; return *this; } }; struct Foo { MyPtr Ptr; }; MyPtr begin(Foo& t) { return t.Ptr; } MyPtr end(Foo& t) { return t.Ptr; } } int main() { std::cout << typeid(decltype(++begin(std::declval<Test::Foo&>()), *begin(std::declval<Test::Foo&>()), std::true_type{})).name() <<std::endl; }
运行后输出:
d
这里的"d"对应double类型(来自重载的逗号运算符返回值),但decltype中最后一个表达式是std::true_type{},为什么推导结果不是std::true_type而是逗号运算符的返回类型?
我的推测是:std::true_type可隐式转换为int,而自定义的MyPtr重载了接受int参数的operator,(),因此原decltype表达式等价于:
std::cout << typeid(decltype(++begin(std::declval<Test::Foo&>()), declval<double&>())).name() <<std::endl;
同时我计划将decltype的内容修改为如下代码,以此避免匹配重载的逗号运算符:
std::cout << typeid(decltype(void(++begin(std::declval<Test::Foo&>())), void(*begin(std::declval<Test::Foo&>())), std::true_type{})).name() <<std::endl;
1. 关于推测的正确性
你的推测完全正确,具体原因:
- 当表达式中存在用户重载的逗号运算符时,编译器会优先匹配重载版本,而非使用内置逗号运算符的规则(内置规则是执行左操作数,返回右操作数类型)。
std::true_type继承自std::integral_constant<bool, true>,可隐式转换为bool,bool又能进一步隐式转换为int,刚好匹配MyPtr::operator,(int)的参数类型。- 因此整个逗号表达式链的最终结果是该重载运算符的返回值类型
double,typeid输出的"d"正是double的类型名缩写。
2. 关于修改方案的正确性
这个方案是正确的,核心逻辑是强制触发内置逗号运算符:
- 内置逗号运算符仅在左右操作数无法匹配用户重载的
operator,()时才会生效。 - 用
void()包裹前两个表达式后,它们的结果被转换为void类型,而重载的逗号运算符无法接收void类型的参数,此时编译器会 fallback 到内置逗号运算符规则:依次执行所有左操作数,最终返回最右侧std::true_type{}的类型。 - 无论是否存在用户重载的逗号运算符,修改后的表达式最终类型都会是
std::true_type,符合预期。
内容的提问来源于stack exchange,提问作者J.M
相关产品推荐
相关产品推荐

