为什么“专业辩论”必须无效?

时间:2019-06-19 12:38:47

标签: c++ templates template-specialization enable-if template-variables

this saga中的另一个问题。 Guillaume Racicot足以为我提供yet another workaround,所以这是我基于以下问题得出的代码:

struct vec
{
    double x;
    double y;
    double z;
};

namespace details
{
template <typename T>
using subscript_function = double(*)(const T&);

template <typename T>
constexpr double X(const T& param) { return param.x; }

template <typename T>
constexpr double Y(const T& param) { return param.y; }

template <typename T>
constexpr double Z(const T& param) { return param.z; }
}

template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };


int main() {
    vec foo = { 1.0, 2.0, 3.0 };

    for(const auto i : my_temp<decltype(foo)>) {
        cout << (*i)(foo) << endl;
    }
}

当我返回除void以外的其他东西时,问题似乎出现在我的专业领域。例如,在上面的代码中,enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>可以防止特殊化,而只需删除最后一个参数并允许enable_if返回void即可进行特殊化。

我认为这表明我对这里实际发生的误解。为什么专用类型必须始终为void才能起作用?

Live Example

3 个答案:

答案 0 :(得分:7)

不确定要了解什么,但是...

如果你写

template <typename T, typename = void>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>>[] = { &details::X<T>, &details::Y<T>, &details::Z<T> };

您有一个第一个主要模板变量,其中包含两个模板:一个类型和一个默认类型(void)。

std::enable_if_tvoid时,启用第二个模板变量。

写作时会发生什么

for(const auto i : my_temp<decltype(foo)>) 

编译器:

1)找到具有单个模板参数的my_temp<decltype(foo)>

2)寻找匹配的my_temp模板变量

3)仅找到具有两个模板参数的my_temp,但第二个具有默认参数,所以

4)决定my_temp<decltype(foo)>只能是my_temp<decltype(foo), void>(如果愿意,可以是my_temp<vec, void>

5)看到主要的my_temp匹配

6)看到my_temp专长不匹配,因为

enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>

T(即vec),因此只能匹配与my_temp<vec, vec>不同的my_temp<vec, void>

7)选择唯一可用的模板变量:主变量。

如果您希望

启用专业化
enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>

您应该使用T

// ..............................V   T! not void
template <typename T, typename = T>
constexpr details::subscript_function<T> my_temp[] = { &details::X<T>, &details::Y<T> };

作为主模板变量中第二个模板类型的默认设置。

非主题建议:最好在std::declval测试中使用std::is_floating_point_v;我建议

std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>

答案 1 :(得分:2)

模板专业化的工作原理:

有一个主要专业化。这基本上定义了参数和默认值。

template <typename T, typename = void>

这是您的主要专业化的模板部分。它使用一种类型,然后是另一种默认为void的类型。

这是模板的“界面”。

template <typename T>
[...] <T, enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>> [...]

这是第二专业

在这种情况下,template <typename T>根本不同。在主要的专业领域,它定义了一个接口;在这里,它定义了下面使用的“变量”。

然后我们要进行模式匹配。这是在模板名称之后(在这种情况下为变量)。重新格式化以保持理智:

<
  T,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(T())
      )
    >,
    T
  >
>

现在我们可以看到结构。有两个参数,与主要专业化中的两个参数匹配。

第一个是T。现在,这与主要专业化中的 name 匹配,但这没有任何意义。就像使用变量make_point(int x, int y)调用函数x,y一样-可能是y,xm,n,而make_point不在乎。

在此专业化中,我们引入了全新的变量T。然后,将其绑定到第一个参数。

第二个参数很复杂。足够复杂,以至于处于“非推断上下文”中。通常,模板专业化参数是根据主要专业化中定义的传递给模板的参数推导出来的。非推论参数不是。

如果我们进行some_template< Foo >,则将类型TFoo进行匹配将得到Foo。很容易的模式匹配。允许进行更高级的模式匹配,例如采用T*的特殊化;无法与some_template<int>匹配,但是与some_template<int*>T=int匹配。

未推断出的论点不参与此游戏。而是插入 do 匹配的参数,并生成结果类型。而且,并且仅当与该插槽中传递给模板的类型匹配时,专业化匹配。

因此,让我们检查一下发生了什么情况,我们将vec作为my_temp的第一个参数传递

首先,我们进入主要专业领域

template<typename T, typename=void>
my_temp

现在my_temp<vec>有一个默认参数。它变成my_temp<vec,void>

然后,我们检查每个 other 专长,以查看它们是否匹配;如果没有,我们将保留为主要专业。

另一个专业是:

template<typename T>
[...] my_temp<
  T,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(T())
      )
    >,
    T
  >
>[...]

使用[...]处理无关紧要的内容。

好的,第一个参数绑定到T。好吧,第一个参数是vec,所以很容易。我们替代:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    is_floating_point_v
    <
      decltype
      (
        details::X(vec())
      )
    >,
    vec
  >
>[...]

然后评估:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    is_floating_point_v
    <
      double
    >,
    vec
  >
>[...]

更多:

template<typename T>
[...] my_temp<
  vec,
  enable_if_t
  <
    true,
    vec
  >
>[...]

更多:

template<typename T>
[...] my_temp<
  vec,
  vec
>[...]

好的,记得我们在哪里尝试与my_temp<vec,void>进行匹配。但是,该专业化评估为my_temp<vec,vec>,但不匹配。 已拒绝。

,T中删除enable_if,或将其设为,void(同样),上述参数的最后一行变为my_temp<vec,void>my_temp<vec,void>相匹配,然后选择次要专业化。


令人困惑。相同的语法意味着主要专业知识和次要专业知识的根本不同。您必须了解模板参数和非推导上下文的模式匹配。

通常,您会得到有人使用它,就像您复制的魔术黑匣子一样。

神奇的黑匣子-模式-非常有用,因为它们意味着您不必考虑如何到达那里的细节。但是,了解模板参数,演绎和非演绎上下文的模式匹配以及主要专业和次要专业之间的差异是了解黑匣子工作原理的关键。

答案 2 :(得分:0)

使用

struct vec
{
    double x;
    double y;
    double z;
};

template <typename T>
constexpr double X(const T& param) { return param.x; }

我们会发现

is_floating_point_v<decltype(details::X(T()))

评估为true(除非您要专门针对X使用vec而不返回浮点...)。

所以我们实际上有:

template <typename T>
constexpr details::subscript_function<T> my_temp<T, enable_if_t<true, T>>[]
    = { /*...*/ };

或更短:

template <typename T>
constexpr details::subscript_function<T> my_temp<T, T>[]
    = { /*...*/ };

(如果有的话,当然...)。明确选择一个:

my_temp<decltype(foo), void>
my_temp<decltype(foo), int>
my_temp<decltype(foo), double>

全部匹配主模板,但没有一个专业化

my_temp<decltype(foo), decltype(foo)>

现在确实匹配专业化(由于X(foo)返回double ...而存在)。

最后回到my_temp<decltype(foo)> –好吧,仅给出一个模板参数。第二种是哪种类型?默认参数告诉您(或更好的是:编译器),它是void。并根据以上...

因此,如果要匹配专业化,则这需要使用void作为第二个模板参数的类型(如您已经发现的那样),或者将非专业化模板中的默认值更改为等于第一个模板参数(typename T, typename = T)。

实际上,只要为 both 选择相同的类型(例如两次int,{ {1}},std::string,...)。