std::common_type的目的是什么?

31

我开始研究std::common_type,但对它的目的和功能并不确定。还有几个问题让我感到奇怪:

  • 参数的顺序很重要:common_type<Foo, Bar, Baz>可能与common_type<Baz, Foo, Bar>不同。其中一个可能编译,另一个可能不编译。虽然这在common_type的定义方式中很清楚,但感觉很奇怪和不直观。这是因为缺乏通用解决方案还是故意的?
  • 实例化可能会导致编译器错误而不是我可以处理的东西。如何检查common_type是否实际上会编译?is_convertible不足以检查common_type是否被专门化?
  • 仍然没有办法解决这种情况下的共同类型:

    struct Baz;
    struct Bar { int m; };
    struct Foo { int m; }; 
    struct Baz { Baz(const Bar&); Baz(const Foo&); };
    
    推荐的解决方案是对 common_type 进行特化,但这很繁琐。是否有更好的解决方案?请参考 N3242 中的 §20.9.7.6 表57。
2个回答

40

std::common_type 是为了与 std::duration 一起使用而引入的。如果你将 std::duration<int>std::duration<short> 相加,结果应该是 std::duration<int>。决定委托给一个单独的模板,通过应用于 ?: 算术条件运算符的核心语言规则来找到结果,而不是指定无限数量的配对。

人们随后发现这个模板可能是通用的,因此将其添加为 std::common_type 并扩展以处理任意数量的类型。在 C++0x 库中,它仅用于类型对。

您可以使用新的 SFINAE 规则检测某些 std::common_type 实例是否有效。我还没有尝试过。在大多数情况下,如果没有“常见类型”,那么您无法执行有意义的操作,因此编译错误是合理的。

std::common_type 不是魔法 --- 它遵循 ?: 的规则。如果 true?a:b 可以编译,则 std::common_type<decltype(a),decltype(b)>::type 将给出结果的类型。


你能提供一下新的SFINAE规则的链接、参考或示例吗?另外,对于我的最后一个问题有什么想法吗?我确实理解为什么common_type不能对common_type<Bar, Foo>产生答案,但我认为这很有用和直观。 - pmr
SFINAE在14.8.2中有描述,特别是第5-8段。虽然不是特别清楚,但第8段中的“如果替换导致无效类型或表达式,则类型推断失败”是关键部分。基本上,如果您编写一个函数模板,该函数模板依赖于common_type<A,B>::type可用,其中至少有一个AB是该函数模板的模板参数,则该函数不会被视为common_type<A,B>::type的重载分辨率有效。如果您提供函数的另一种重载,则将选择该重载。 - Anthony Williams
如果你想让 common_type<Bar, Foo>::type 输出 Baz,那么你必须自己专门定义common_type。否则,编译器怎么知道从FooBar构造出的众多类型中使用哪一个呢?例如:boost::any可以从任何东西构造。 - Anthony Williams
3
点赞,我还要补充一点:common_type 更多地是供库构建者使用的工具,而不是供库用户使用的工具。通常情况下,库用户只有在库作者将 common_type 设计到库接口中时才会使用它。库作者可以根据 common_type 是否真正对他的库有用来选择是否使用它。 - Howard Hinnant
对于C++03,我的仿真可以使用:https://dev59.com/7UzSa4cB1Zd3GeqPnIGx#2450157。但它有一个缺点:无法处理引用/左值! - Johannes Schaub - litb
显示剩余2条评论

6
这里有一些关于std::common_type的用例:

1. 可变参数包的总和

这里是一个需要使用common_type的可变参数总和版本:

template<typename... T>
constexpr auto sum(T&&... values) {
    std::common_type_t<T...> sum {}; // <= here we need std::common_type
    // see code in the link above for the machinery of the below code
    static_for<sizeof...(T)>([&](auto index) {
        sum += get<index>(values...);
    });
    return sum;
}

上面的例子使用了来自thisthis的机器设备。

注意:你可以使用以下代码来实现相同的功能,而不需要使用common_type

template<typename T>
auto sum(T&& t) {
    return t;
}

template<typename T, typename... Ts>
auto sum(T&& t, Ts&&... ts) {
    return t + sum(std::forward<Ts>(ts)...);
}

2. 要求可变参数包具有公共类型

下面的代码 基于 这个 Stack Overflow 帖子

template <typename AlwaysVoid, typename... Ts>
struct has_common_type_impl : std::false_type {};

template <typename... Ts>
struct has_common_type_impl<std::void_t<std::common_type_t<Ts...>>, Ts...>
    : std::true_type {};

template <typename... Ts>
concept has_common_type = 
    sizeof...(Ts) < 2 ||
    has_common_type_impl<void, Ts...>::value;

template<typename... Ts> requires has_common_type<Ts...>
void foo(Ts&&... ts) {}

3. 从可变包中创建make_array

曾经有一个未决议案提出了函数make_array。关于是否仍需要make_array的讨论,请参见此SO帖子

make_array的简单实现看起来像这样

template<typename... T>
constexpr auto make_array(T&&... values) requires has_common_type<T...> {
    using TYPE = std::common_type_t<std::decay_t<T>...>;
    return std::array<TYPE, sizeof...(T)>{static_cast<TYPE>(values)...};
}

以下是使用示例:

constexpr auto arr1 = make_array(1, 2, 3);
constexpr auto arr2 = make_array(1, 2.5, 3);
using namespace std::string_literals;
auto arr3 = make_array("hello"s, "world");

请注意,有关 make_array 的提案中有一个选项可以提供实际请求的类型,但是如果未提供,则应使用 common_type

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