std::conditional 编译时分支评估

cma*_*t85 6 c++ conditional templates c++11

编译这个:

template < class T, class Y, class ...Args >
struct isSame
{
    static constexpr bool value = std::conditional<
                                      sizeof...( Args ),
                                      typename std::conditional<
                                          std::is_same< T, Y >::value,
                                          isSame< Y, Args... >, // Error!
                                          std::false_type >::type,
                                      std::is_same< T, Y > >::type::value;
};

int main()
{
    qDebug() << isSame< double, int >::value;
    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

给我这个编译器错误:

error: wrong number of template arguments (1, should be 2 or more)
Run Code Online (Sandbox Code Playgroud)

问题是isSame< double, int >有一个空的Args参数包,所以isSame< Y, Args... >实际上变得isSame< Y >与签名不匹配。

但我的问题是:为什么要评估该分支? sizeof...( Args )false,因此std:conditional不应评估内部。这不是一个运行一段代码,编译器知道sizeof..( Args )永远true给定模板类型。

如果你很好奇,它应该是 的可变参数版本std::is_same,而不是它的工作原理......

Fil*_*efp 5

不要责怪std::conditional

简而言之,这是因为模板参数替换发生在 之外std::conditional,这意味着它不是std::conditional导致错误的原因......而是isSame

想象一下std::conditional,作为一个函数调用(事实并非如此),我们作为参数传递的内容函数的实际主体被求值之前就被求值,并且我们作为参数传递的内容肯定必须是有效的构造,即使函数本身不是这样。不要使用它们。

进一步阅读可在以下链接的答案中找到:


一个办法

添加一些间接,这样你就不会实例化isSameif sizeof... (Args) == 0,你可以使用一个特征 as isSame_if_not_empty<Args..>::type,它会产生isSame<Args...>ifArgs不为空,如果它确实为空,则产生其他东西。


建议的解决方案

修复它,以便isSame可以与空的变量包一起使用,产生 true,这是明智的方法。如果描述isSame“传递的所有类型都是同一类型”,则空包肯定具有“所有类型”相同的类型。


Man*_*726 5

但我的问题是:为什么要评估该分支?

因为没有评价,那根本就不是一个分支。那是一个模板,它的实例化成功地实例化了std::conditional模板
我的意思是,在编写模板元程序时,评估抽象很有用,但您永远不要忘记模板系统真正在做什么

如果您需要有条件地实例化一个可能格式错误的模板,请添加一个间接级别。检查答案以获取示例。


Jar*_*d42 5

您有一个错误,因为在用作模板参数时类型必须正确。
您可以使用模板专业化来解决您的问题,例如:

#include <type_traits>

template <typename ... Ts> struct are_same;

template <> struct are_same<> : std::true_type {};
template <typename T> struct are_same<T> : std::true_type {};

template <typename T1, typename T2, typename... Ts>
struct are_same<T1, T2, Ts...> :
    std::conditional<
        std::is_same<T1, T2>::value,
        are_same<T2, Ts...>,
        std::false_type
    >::type
{};

static_assert(are_same<char, char, char>::value, "all type should be identical");
static_assert(!are_same<char, char, int>::value, "all type should not be identical");
Run Code Online (Sandbox Code Playgroud)


Yak*_*ont 5

问题是条件只是一个模板。它的类型取决于所有 3 个参数,即使::typefield 仅取决于其中 2 个(thebool和 无论它选择哪个)。

要执行您想要的操作,请将表示如何将参数应用于双方的类型传递给它。然后将参数传递给结果。

template<template<class...>class Target>struct defer{
  template<class...Ts>using execute=Target<Ts...>;
};
template<class T>struct sink{
  template<class...>using execute=T;
};
template< class Prog, class... Ts > using run=Prog::template execute<Ts...>;

using choice = typename std::conditional< test, defer<Foo>, sink< std::false_type >  >::type;
using result = run< choice, int, double >;
Run Code Online (Sandbox Code Playgroud)

给我们一个返回 a deferdFoo或 a的条件sink d的条件false_type。然后我们可以使用一些参数来运行它,给我们Foo<int, double>false_type

sinkexecute忽略参数并始终返回传入的类型,同时defer将它们应用于传入的template.

这里的技巧是Foo<int, double>如果为 false 则不会运行test,因为在这种情况下choicesink<false_type>

template<bool b, class A, class B, class...Ts>
using pick = run< typename std::conditional< b, A, B >::type, Ts... >;

using result=pick<test, defer<Foo>, sink<std::false_type>, int, double >;
Run Code Online (Sandbox Code Playgroud)

choice与上面的/result在一行中执行相同的操作。

为了解决您的问题,我们推迟执行您的 if 分支之一:

static constexpr bool value =
typename std::conditional< sizeof...( Args ),
  pick< std::is_same< T, Y >::value,
    defer<isSame>,
    sink<std::false_type>,
    Y, Args...
  >,
  std::is_same< T, Y >
>::type::value;
Run Code Online (Sandbox Code Playgroud)

现在,通常有很多方法可以避免这种技术,但您应该知道它的存在。