SFINAE 可以干扰偏序吗?
Can SFINAE interfere with partial ordering?
我正在开发一个库,该库使用名为 PETE 的相当古老的 C++ 表达式模板 (ET) 引擎。 (我试图找到它的源代码 link 以便我可以引用它,但我只找到了关于它的文章。)
快速概述:使用 ET,C++ 编译器使用运算符中缀形式 (+,-,*,/) 从表达式构建表示表达式及其操作的 C++ 类型。 PETE 方法的核心是 ForEach
class 模板,用于稍后解析和评估表达式。
我想做的是提供一个专门的 ForEach
,当它的参数满足特定条件时使用它。我正在尝试使用部分专业化和 enable_if
的使用,但编译器抱怨 'ambiguous partial specialization'.
如果需要,我很乐意 post 代码的其他部分,但我会坚持使用有问题的直接 class 模板(注意:我添加了 Enable
参数,以便使以后的特化 select 能够使用 enable_if
。注意 2:为了更短 post,我不包括该方法的实现):
template<class Expr, class FTag, class CTag, class Enable = void>
struct ForEach
{
typedef typename LeafFunctor<Expr, FTag>::Type_t Type_t;
inline static
Type_t apply(const Expr &expr, const FTag &f, const CTag &)
{
// empty
}
};
然后是第一个部分专业化(也是标准 PETE 的一部分)。这就是后来所谓的数字“1”:
// 1
template<class Op, class A, class B, class FTag, class CTag>
struct ForEach<BinaryNode<Op, A, B>, FTag, CTag >
{
typedef typename ForEach<A, FTag, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, FTag, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const FTag &f,
const CTag &c)
{
// default implementation for BinaryNode
}
};
这是我在编译器抱怨的地方附加的部分专业化。它实际上抱怨数字“2”与数字“1”不明确:
// A
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< ! EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f,
const CTag &c)
{
// default implementation for BinaryNode (this is the same as above)
}
};
// 2
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
// special implementation for when EvalToSpinMatrix<A>::value is true
}
};
编译错误如下(注意:为了提高可读性,我重新格式化了它)
ambiguous template instantiation for ‘struct ForEach<BinaryNode<OpMultiply, Vector<double>, Vector<double> >, ViewSpinLeaf, OpCombine, void>’
candidate '1':
candidates are: template<class Op, class A, class B, class FTag, class CTag> struct ForEach<BinaryNode<Op, A, B>, FTag, CTag> ;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
FTag = ViewSpinLeaf;
CTag = OpCombine;
candidate '2':
note: template<class A, class B, class CTag> struct ForEach<BinaryNode<OpMultiply, T1, T2>, ViewSpinLeaf, CTag, typename std::enable_if<EvalToSpinMatrix<A>::value, void>::type>;
A = Vector<double>;
B = Vector<double>;
CTag = OpCombine;
根据我的理解,该标准适用所谓的 'partial ordering',它表示部分专业化比另一个专业化更专业,如果它至少与另一个专业化一样,但反之则不然。应用于此示例表示:
数字 2 至少与数字 1 一样专业,因为对于每个参数集(对于数字 2)我都可以找到一个匹配的集合(对于数字 1)。但是数字 1 至少不像数字 2 那样专业。如果我将 FTag
设置为 ViewSpinLeaf
以外的任何值,那么数字 2 将无法匹配。因此,2 号更专业。所以,我不明白为什么编译器不这么看。
作为第二次测试,我删除了专业化 'A'(带有负值 enable_if
的那个)并从专业化“2”中删除了 enable_if_t
位。这编译得很好,这意味着数字 '2' 中的所有其他 statements/typedefs 都可以工作。然而,这不是我所需要的,因为此代码路径随后适用于所有 BinaryNode<OpMultiply,..>
而不仅仅是特定情况。
以防万一。我使用的编译器是 Ubuntu 上的 g++ 9.3,启用了标准 C++14。
编辑:正如评论中所建议的那样,BinaryNode<Op,..>
和 BinaryNode<OpMultiply,..>
之间可能存在歧义。我将数字“2”更改为以下内容:
// 2
template<class Op, class A, class B, class CTag>
struct ForEach<BinaryNode<Op, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
}
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
}
};
现在只有 FTag
更专业。编译器抱怨同样的歧义:
note: candidates are: ‘template<class Op, class A, class B, class FTag, class CTag> struct ForEach<BinaryNode<Op, A, B>, FTag, CTag>;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
FTag = ViewSpinLeaf;
CTag = OpCombine;
‘template<class Op, class A, class B, class CTag> struct ForEach<BinaryNode<Op, A, B>, ViewSpinLeaf, CTag, typename std::enable_if<EvalToSpinMatrix<A>::value, void>::type>;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
CTag = OpCombine;
数字“2”显然更专业。
EDIT2:添加一个最小的复制器。有一个 #if 0
如果像这样保留程序编译并采用默认代码路径。但是,当使用 #if 1
打开部分专业化时,会再现歧义。
#include<type_traits>
#include<iostream>
using namespace std;
template<class T> class Vector {};
struct ViewSpinLeaf {};
struct OpCombine {};
template<class LeafType, class LeafTag> struct LeafFunctor {};
template<class A, class B, class Op, class Tag> struct Combine2 {};
template<class T1, class T2, class Op>
struct BinaryReturn {
typedef T1 Type_t;
};
template<class T>
struct LeafFunctor<Vector<T>, ViewSpinLeaf>
{
typedef T Type_t;
inline static
Type_t apply(const Vector<T> & s, const ViewSpinLeaf& v)
{
return Type_t();
}
};
template<class A,class B,class Op>
struct Combine2<A, B, Op, OpCombine>
{
typedef typename BinaryReturn<A, B, Op>::Type_t Type_t;
inline static
Type_t combine(const A& a, const B& b, const Op& op, const OpCombine& do_not_use)
{
return op(a, b);
}
};
struct OpMultiply
{
template<class T1, class T2>
inline typename BinaryReturn<T1, T2, OpMultiply >::Type_t
operator()(const T1 &a, const T2 &b) const
{
return (a * b);
}
};
template<class Op, class Left, class Right>
class BinaryNode
{
public:
BinaryNode(const Op &o, const Left &l, const Right &r) : op_m(o), left_m(l), right_m(r) {}
private:
Op op_m;
Left left_m;
Right right_m;
};
template<class Expr, class FTag, class CTag, class Enable = void >
struct ForEach
{
typedef typename LeafFunctor<Expr, FTag>::Type_t Type_t;
inline static
Type_t apply(const Expr &expr, const FTag &f, const CTag &)
{
return LeafFunctor<Expr, FTag>::apply(expr, f);
}
};
template<class Expr, class FTag, class CTag>
inline typename ForEach<Expr,FTag,CTag>::Type_t
forEach(const Expr &e, const FTag &f, const CTag &c)
{
return ForEach<Expr, FTag, CTag>::apply(e, f, c);
}
template<class Op, class A, class B, class FTag, class CTag>
struct ForEach<BinaryNode<Op, A, B>, FTag, CTag >
{
typedef typename ForEach<A, FTag, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, FTag, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const FTag &f,
const CTag &c)
{
std::cout << "I don't want to be here. " << std::endl;
return Type_t();
}
};
#if 0
template<class A>
struct EvalToSpinMatrix
{
constexpr static bool value = false;
};
template<>
struct EvalToSpinMatrix<Vector<double> >
{
constexpr static bool value = true;
};
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
std::cout << "I want to get here. " << std::endl;
return Type_t();
}
};
#endif
int main(int argc, char **argv)
{
OpMultiply op;
Vector<double> left;
Vector<double> right;
BinaryNode< OpMultiply , Vector<double> , Vector<double> > rhs(op,left,right);
forEach( rhs , ViewSpinLeaf() , OpCombine() );
}
我应该说 select 使用基于 EvalToSpinMatrix
特征的 enable_if
开关的偏特化很重要。显然在实际应用中这个特性是比较复杂的。很好,它在这个简单版本中重现了歧义。
一段时间后我想我有一个答案。
首先,我将代码简化到最低限度,删除了重现错误不需要的任何内容。这一切都归结为为什么部分特化在下面是模棱两可的(我很抱歉更改了各个位的名称,但取消膨胀你的代码并不是一件容易的事,至少对我来说):
#include <utility>
template<typename T, typename = void>
struct A {};
template<typename T, typename U>
struct A<std::pair<T,U>> {};
template<typename U>
struct A<std::pair<int,U>, std::enable_if_t<std::is_same_v<int,U>>> {};
int main() {
A<std::pair<int, int>> x;
}
看起来很像第二个偏特化比第一个更特化,但实际上并非如此。
让我们转到 部分排序 部分 on cppreference 并阅读所有内容:
Informally "A is more specialized than B" means "A accepts a subset of the types that B accepts".
Formally, to establish more-specialized-than relationship between partial specializations, each is first converted to a fictitious function template as follows:
- the first function template has the same template parameters as the first partial specialization and has just one function parameter, whose type is a class template specialization with all the template arguments from the first partial specialization
- [the same as above, but
s/first/second/g
].
The function templates are then ranked as if for function template overloading.
接下来是一个有趣的例子。
在简化代码的情况下,这意味着与第一个特化对应的虚构函数模板具有签名
template<typename T, typename U>
void f(A<std::pair<T,U>>);
而对应于第二个专业化的那个有签名
template<typename U>
void f(A<std::pair<int,U>, std::enable_if_t<std::is_same_v<int,U>>>) {}
这是函数模板的两个不同的重载。因此,问题已通过上面第二个 link 中描述的规则转移到它们中的哪一个是首选。
老实说,在这一点上我有点迷茫,所以我问了,答案是即使第二个重载将std::pair
的第一个模板参数固定为int
,第一个重载是将 A
的第二个模板参数固定为 void
,因此其中 none 比另一个更专业。 std::enable_if
/std::enable_if_t
不会改变这种情况,因为它被用作类型 (void
,因为我们没有将第二个模板参数传递给 std::enable_if
/std::enable_if_t
) 的函数参数,而不是模板类型参数。
我正在开发一个库,该库使用名为 PETE 的相当古老的 C++ 表达式模板 (ET) 引擎。 (我试图找到它的源代码 link 以便我可以引用它,但我只找到了关于它的文章。)
快速概述:使用 ET,C++ 编译器使用运算符中缀形式 (+,-,*,/) 从表达式构建表示表达式及其操作的 C++ 类型。 PETE 方法的核心是 ForEach
class 模板,用于稍后解析和评估表达式。
我想做的是提供一个专门的 ForEach
,当它的参数满足特定条件时使用它。我正在尝试使用部分专业化和 enable_if
的使用,但编译器抱怨 'ambiguous partial specialization'.
如果需要,我很乐意 post 代码的其他部分,但我会坚持使用有问题的直接 class 模板(注意:我添加了 Enable
参数,以便使以后的特化 select 能够使用 enable_if
。注意 2:为了更短 post,我不包括该方法的实现):
template<class Expr, class FTag, class CTag, class Enable = void>
struct ForEach
{
typedef typename LeafFunctor<Expr, FTag>::Type_t Type_t;
inline static
Type_t apply(const Expr &expr, const FTag &f, const CTag &)
{
// empty
}
};
然后是第一个部分专业化(也是标准 PETE 的一部分)。这就是后来所谓的数字“1”:
// 1
template<class Op, class A, class B, class FTag, class CTag>
struct ForEach<BinaryNode<Op, A, B>, FTag, CTag >
{
typedef typename ForEach<A, FTag, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, FTag, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const FTag &f,
const CTag &c)
{
// default implementation for BinaryNode
}
};
这是我在编译器抱怨的地方附加的部分专业化。它实际上抱怨数字“2”与数字“1”不明确:
// A
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< ! EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f,
const CTag &c)
{
// default implementation for BinaryNode (this is the same as above)
}
};
// 2
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
// special implementation for when EvalToSpinMatrix<A>::value is true
}
};
编译错误如下(注意:为了提高可读性,我重新格式化了它)
ambiguous template instantiation for ‘struct ForEach<BinaryNode<OpMultiply, Vector<double>, Vector<double> >, ViewSpinLeaf, OpCombine, void>’
candidate '1':
candidates are: template<class Op, class A, class B, class FTag, class CTag> struct ForEach<BinaryNode<Op, A, B>, FTag, CTag> ;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
FTag = ViewSpinLeaf;
CTag = OpCombine;
candidate '2':
note: template<class A, class B, class CTag> struct ForEach<BinaryNode<OpMultiply, T1, T2>, ViewSpinLeaf, CTag, typename std::enable_if<EvalToSpinMatrix<A>::value, void>::type>;
A = Vector<double>;
B = Vector<double>;
CTag = OpCombine;
根据我的理解,该标准适用所谓的 'partial ordering',它表示部分专业化比另一个专业化更专业,如果它至少与另一个专业化一样,但反之则不然。应用于此示例表示:
数字 2 至少与数字 1 一样专业,因为对于每个参数集(对于数字 2)我都可以找到一个匹配的集合(对于数字 1)。但是数字 1 至少不像数字 2 那样专业。如果我将 FTag
设置为 ViewSpinLeaf
以外的任何值,那么数字 2 将无法匹配。因此,2 号更专业。所以,我不明白为什么编译器不这么看。
作为第二次测试,我删除了专业化 'A'(带有负值 enable_if
的那个)并从专业化“2”中删除了 enable_if_t
位。这编译得很好,这意味着数字 '2' 中的所有其他 statements/typedefs 都可以工作。然而,这不是我所需要的,因为此代码路径随后适用于所有 BinaryNode<OpMultiply,..>
而不仅仅是特定情况。
以防万一。我使用的编译器是 Ubuntu 上的 g++ 9.3,启用了标准 C++14。
编辑:正如评论中所建议的那样,BinaryNode<Op,..>
和 BinaryNode<OpMultiply,..>
之间可能存在歧义。我将数字“2”更改为以下内容:
// 2
template<class Op, class A, class B, class CTag>
struct ForEach<BinaryNode<Op, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
}
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
}
};
现在只有 FTag
更专业。编译器抱怨同样的歧义:
note: candidates are: ‘template<class Op, class A, class B, class FTag, class CTag> struct ForEach<BinaryNode<Op, A, B>, FTag, CTag>;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
FTag = ViewSpinLeaf;
CTag = OpCombine;
‘template<class Op, class A, class B, class CTag> struct ForEach<BinaryNode<Op, A, B>, ViewSpinLeaf, CTag, typename std::enable_if<EvalToSpinMatrix<A>::value, void>::type>;
Op = OpMultiply;
A = Vector<double>;
B = Vector<double>;
CTag = OpCombine;
数字“2”显然更专业。
EDIT2:添加一个最小的复制器。有一个 #if 0
如果像这样保留程序编译并采用默认代码路径。但是,当使用 #if 1
打开部分专业化时,会再现歧义。
#include<type_traits>
#include<iostream>
using namespace std;
template<class T> class Vector {};
struct ViewSpinLeaf {};
struct OpCombine {};
template<class LeafType, class LeafTag> struct LeafFunctor {};
template<class A, class B, class Op, class Tag> struct Combine2 {};
template<class T1, class T2, class Op>
struct BinaryReturn {
typedef T1 Type_t;
};
template<class T>
struct LeafFunctor<Vector<T>, ViewSpinLeaf>
{
typedef T Type_t;
inline static
Type_t apply(const Vector<T> & s, const ViewSpinLeaf& v)
{
return Type_t();
}
};
template<class A,class B,class Op>
struct Combine2<A, B, Op, OpCombine>
{
typedef typename BinaryReturn<A, B, Op>::Type_t Type_t;
inline static
Type_t combine(const A& a, const B& b, const Op& op, const OpCombine& do_not_use)
{
return op(a, b);
}
};
struct OpMultiply
{
template<class T1, class T2>
inline typename BinaryReturn<T1, T2, OpMultiply >::Type_t
operator()(const T1 &a, const T2 &b) const
{
return (a * b);
}
};
template<class Op, class Left, class Right>
class BinaryNode
{
public:
BinaryNode(const Op &o, const Left &l, const Right &r) : op_m(o), left_m(l), right_m(r) {}
private:
Op op_m;
Left left_m;
Right right_m;
};
template<class Expr, class FTag, class CTag, class Enable = void >
struct ForEach
{
typedef typename LeafFunctor<Expr, FTag>::Type_t Type_t;
inline static
Type_t apply(const Expr &expr, const FTag &f, const CTag &)
{
return LeafFunctor<Expr, FTag>::apply(expr, f);
}
};
template<class Expr, class FTag, class CTag>
inline typename ForEach<Expr,FTag,CTag>::Type_t
forEach(const Expr &e, const FTag &f, const CTag &c)
{
return ForEach<Expr, FTag, CTag>::apply(e, f, c);
}
template<class Op, class A, class B, class FTag, class CTag>
struct ForEach<BinaryNode<Op, A, B>, FTag, CTag >
{
typedef typename ForEach<A, FTag, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, FTag, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, Op, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<Op, A, B> &expr, const FTag &f,
const CTag &c)
{
std::cout << "I don't want to be here. " << std::endl;
return Type_t();
}
};
#if 0
template<class A>
struct EvalToSpinMatrix
{
constexpr static bool value = false;
};
template<>
struct EvalToSpinMatrix<Vector<double> >
{
constexpr static bool value = true;
};
template<class A, class B, class CTag>
struct ForEach<BinaryNode<OpMultiply, A, B>, ViewSpinLeaf, CTag , enable_if_t< EvalToSpinMatrix<A>::value > >
{
typedef typename ForEach<A, ViewSpinLeaf, CTag>::Type_t TypeA_t;
typedef typename ForEach<B, ViewSpinLeaf, CTag>::Type_t TypeB_t;
typedef typename Combine2<TypeA_t, TypeB_t, OpMultiply, CTag>::Type_t Type_t;
inline static
Type_t apply(const BinaryNode<OpMultiply, A, B> &expr, const ViewSpinLeaf &f, const CTag &c)
{
std::cout << "I want to get here. " << std::endl;
return Type_t();
}
};
#endif
int main(int argc, char **argv)
{
OpMultiply op;
Vector<double> left;
Vector<double> right;
BinaryNode< OpMultiply , Vector<double> , Vector<double> > rhs(op,left,right);
forEach( rhs , ViewSpinLeaf() , OpCombine() );
}
我应该说 select 使用基于 EvalToSpinMatrix
特征的 enable_if
开关的偏特化很重要。显然在实际应用中这个特性是比较复杂的。很好,它在这个简单版本中重现了歧义。
一段时间后我想我有一个答案。
首先,我将代码简化到最低限度,删除了重现错误不需要的任何内容。这一切都归结为为什么部分特化在下面是模棱两可的(我很抱歉更改了各个位的名称,但取消膨胀你的代码并不是一件容易的事,至少对我来说):
#include <utility>
template<typename T, typename = void>
struct A {};
template<typename T, typename U>
struct A<std::pair<T,U>> {};
template<typename U>
struct A<std::pair<int,U>, std::enable_if_t<std::is_same_v<int,U>>> {};
int main() {
A<std::pair<int, int>> x;
}
看起来很像第二个偏特化比第一个更特化,但实际上并非如此。
让我们转到 部分排序 部分 on cppreference 并阅读所有内容:
Informally "A is more specialized than B" means "A accepts a subset of the types that B accepts".
Formally, to establish more-specialized-than relationship between partial specializations, each is first converted to a fictitious function template as follows:
- the first function template has the same template parameters as the first partial specialization and has just one function parameter, whose type is a class template specialization with all the template arguments from the first partial specialization
- [the same as above, but
s/first/second/g
].The function templates are then ranked as if for function template overloading.
接下来是一个有趣的例子。
在简化代码的情况下,这意味着与第一个特化对应的虚构函数模板具有签名
template<typename T, typename U>
void f(A<std::pair<T,U>>);
而对应于第二个专业化的那个有签名
template<typename U>
void f(A<std::pair<int,U>, std::enable_if_t<std::is_same_v<int,U>>>) {}
这是函数模板的两个不同的重载。因此,问题已通过上面第二个 link 中描述的规则转移到它们中的哪一个是首选。
老实说,在这一点上我有点迷茫,所以我问了std::pair
的第一个模板参数固定为int
,第一个重载是将 A
的第二个模板参数固定为 void
,因此其中 none 比另一个更专业。 std::enable_if
/std::enable_if_t
不会改变这种情况,因为它被用作类型 (void
,因为我们没有将第二个模板参数传递给 std::enable_if
/std::enable_if_t
) 的函数参数,而不是模板类型参数。