为啥专业化论证必须无效?
Posted
技术标签:
【中文标题】为啥专业化论证必须无效?【英文标题】:Why Must Specializing Argument be void?为什么专业化论证必须无效? 【发布时间】:2019-11-02 04:53:48 【问题描述】: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
以外的other 内容时,问题似乎出现在我的专业领域。例如,在上面的代码中,enable_if_t<is_floating_point_v<decltype(details::X(T()))>, T>
防止特化,而简单地删除最后一个参数并允许 enable_if
返回 void
允许特化。
我认为这表明我对这里真正发生的事情的误解。为什么专用类型必须始终为 void
才能正常工作?
Live Example
【问题讨论】:
【参考方案1】:不确定你不明白的地方,但是......
如果你写
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_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, void>
不同的my_temp<vec, vec>
。
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::is_floating_point_v
测试中使用std::declval
;我建议
std::enable_if_t<std::is_floating_point_v<decltype(details::X(std::declval<T>()))>>
【讨论】:
【参考方案2】:有
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
)。
实际上,您可以选择 any 类型作为默认和专业化,只要您为 both 选择相同的类型(例如两次int
、std::string
, MyVeryComplexCustomClass
, ...)。
【讨论】:
【参考方案3】:模板专业化的工作原理:
有一个主要专业化。这一个基本上定义了参数和默认值。
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 匹配,但这没有任何意义。这就像调用带有变量 x,y
的函数 make_point(int x, int y)
—— 它可以是 y,x
或 m,n
和 make_point
无关紧要。
我们在这个专业化中引入了一个全新的变量T
。然后我们将它绑定到第一个参数。
第二个参数很复杂。足够复杂,以至于它处于“非推断上下文”中。通常,模板特化参数是从传递给模板的参数推导出来的,如主特化中定义的那样;非推导论点不是。
如果我们执行some_template< Foo >
,则将类型T
与Foo
匹配得到...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>
,并且那些不匹配。 拒绝。
从enable_if
中删除,T
,或者改成,void
(同样的事情),上面参数的最后一行变成my_temp<vec,void>
匹配my_temp<vec,void>
,并且选择次要专业而不是主要专业一个。
这令人困惑。相同的语法在主要专业化和次要专业化中意味着根本不同的事物。您必须了解模板参数和非推导上下文的模式匹配。
而你通常得到的是有人使用它,就像你复制的魔法黑匣子。
神奇的黑匣子——模式——很有用,因为它们意味着您不必考虑如何到达那里的细节。但是了解模板参数的模式匹配、推导和非推导上下文以及主要和次要特化之间的差异是了解黑盒工作原理的关键。
【讨论】:
以上是关于为啥专业化论证必须无效?的主要内容,如果未能解决你的问题,请参考以下文章