5

所以这个传奇中的另一个问题。Guillaume Racicot已经足够好,可以为我提供另一种解决方法,所以这是我提出这个问题的代码:

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

4

3 回答 3

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) 的类型。

第二个模板变量在 is 时std::enable_if_t启用void

写作时发生了什么

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::declvalstd::is_floating_point_v测试内使用;我建议

std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>
于 2019-06-19T12:50:31.530 回答
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。现在,这与主要专业中的名称匹配,但这没有任何意义。这就像调用一个make_point(int x, int y)带有变量的函数——x,y它可能是也可能y,x不在乎。m,nmake_point

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

第二个论点很复杂。足够复杂,以至于它处于“非推断上下文”中。通常,模板特化参数是从主要特化中定义的传递给模板的参数推导出来的;非演绎论证不是。

如果我们这样做some_template< Foo >了,匹配一个类型TFoo得到Foo...。非常简单的模式匹配。允许更高级的模式匹配,例如采用T*;的特化。this 与 匹配失败some_template<int>,但与some_template<int*>with匹配T=int

非演绎论点不参与此博弈。相反,插入匹配的参数,生成结果类型。当且仅当它与传递给该插槽中模板的类型匹配时,特化才匹配。

因此,让我们检查一下我们vec作为第一个参数传递给my_temp

首先我们去初级专业

template<typename T, typename=void>
my_temp

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

然后我们检查彼此专业,看看是否有任何匹配;如果没有,我们将继续作为主要专业。

另一个专业是:

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>,而那些不匹配。 被拒绝。

删除,Tfrom enable_if,或者 make it ,void(同样的事情),上述参数的最后一行变成了my_temp<vec,void>matches my_temp<vec,void>,并且选择了次要特化而不是主要特化。


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

你通常得到的是有人使用它,就像你复制的一个神奇的黑匣子。

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

于 2019-06-19T13:24:47.027 回答
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)返回双精度......而存在)。

最后回到my_temp<decltype(foo)>- 好吧,只给出了一个模板参数。第二个是什么类型?默认参数告诉您(或更好:编译器),它是void. 而根据上面...

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

实际上,您可以为默认和专业选择任何类型,只要您为两者选择相同的类型(例如两次,,,,int... std::stringMyVeryComplexCustomClass

于 2019-06-19T13:47:01.220 回答