GCC与Clang对三元运算符模板代码的编译哪个符合C++标准?
哪个编译器的实现符合C++标准?
首先看问题中的代码:
#include <unordered_map> #include <iostream> template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args> const T& getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, const T& defval) { static_assert(std::is_convertible_v<KeyP, Key>); auto i = map.find(key); return i != map.end() ? i->second : defval; } template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args> T getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, T&& defval) { static_assert(std::is_convertible_v<KeyP, Key>); auto i = map.find(key); return i != map.end() ? i->second : std::move(defval); } template<template<class, class, class...> class Map, typename Key, typename T, typename KeyP, typename... Args> T getMapEntry(const Map<Key, T, Args...>& map, const KeyP& key, const T&& defval) = delete; class Foo { public: Foo() { std::cout << __PRETTY_FUNCTION__ << '\n'; } ~Foo() { std::cout << __PRETTY_FUNCTION__ << '\n'; } Foo(const Foo&) { std::cout << __PRETTY_FUNCTION__ << '\n'; } Foo& operator=(const Foo&) { std::cout << __PRETTY_FUNCTION__ << '\n'; return *this; } Foo(Foo&&) { std::cout << __PRETTY_FUNCTION__ << '\n'; } Foo& operator=(Foo&&) { std::cout << __PRETTY_FUNCTION__ << '\n'; return *this; } }; int main() { std::unordered_map<int, Foo> map; auto&& ret = getMapEntry(map, 123U, Foo{}); return 0; }
编译器输出
- GCC输出:
Foo::Foo() Foo::Foo(Foo&&) Foo::~Foo() Foo::~Foo()
- Clang输出:
Foo::Foo() Foo::Foo(const Foo &) Foo::~Foo() Foo::~Foo()
问题分析
首先明确:main中的map是空的,因此条件表达式会选择std::move(defval)分支。关键在于条件表达式的类型和值类别推导:
分支类型:
i->second是const Foo&(左值,因为函数参数map是const引用,迭代器为const_iterator)std::move(defval)是Foo&&(xvalue,defval是Foo&&类型,std::move后仍为右值引用)
C++标准中的条件表达式转换规则:
当条件表达式的两个分支类型不同时,会尝试将其中一个分支转换为另一个分支的类型:- 先尝试将
i->second(const Foo左值)转换为std::move(defval)的类型(Foo&&):不成立,因为非const右值引用无法绑定到const左值。 - 再尝试将
std::move(defval)(Foo&&)转换为i->second的类型(const Foo&):成立,右值允许绑定到const左值引用。
- 先尝试将
因此,条件表达式的最终结果类型是const Foo&,值类别为左值。当函数返回类型为Foo时,必须从const Foo&拷贝构造返回值,这与Clang的行为一致。
而GCC错误地将条件表达式的结果处理为Foo&&,导致调用移动构造函数,不符合C++标准。
补充说明
- 使用
if/else分支时,走else分支直接返回std::move(defval),此时返回语句会将Foo&&转换为Foo,调用移动构造函数,这和条件表达式的规则无关。 - 若修改返回语句为
return i != map.end() ? T{i->second} : std::move(defval);,T{i->second}是Foo类型的prvalue,与std::move(defval)的Foo&&共同构成的条件表达式结果为Foo类型的prvalue,返回时会触发移动构造(或拷贝消除),因此调用移动构造函数。
内容的提问来源于stack exchange,提问作者MarkB
相关产品推荐
相关产品推荐

