【问题标题】:Can SFINAE interfere with partial ordering?SFINAE 会干扰偏序吗?
【发布时间】:2021-05-31 15:14:27
【问题描述】:

我正在开发一个库,该库使用名为 PETE 的非常古老的 C++ 表达式模板 (ET) 引擎。 (我试图找到它的源代码的链接,以便我可以引用它,但我只找到了关于它的文章。)

快速概述:使用 ET,C++ 编译器使用操作符中缀形式(+、-、*、/)从表达式构建,这是一种表示表达式及其操作的 C++ 类型。 PETE 方法的核心是 ForEach 类模板,用于稍后解析和评估表达式。

我想做的是提供一个专门的ForEach,当它的参数满足特定条件时使用它。我正在尝试使用部分专业化并使用enable_if,但编译器抱怨“不明确的部分专业化”。

如果需要,我很乐意发布代码的其他部分,但我会坚持使用有问题的直接类模板(注意:我添加了 Enable 参数,以便使用 @987654325 选择后面的专业@. NB2:为了更短的帖子,我不包括方法的实现):

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;

据我了解,该标准应用了所谓的“部分排序”,它表示如果一个部分专业化与另一个专业化程度最低,但反之则不然。应用于这个例子是这样说的:

数字 2 至少与数字 1 一样专业,因为对于每个参数集(对于数字 2),我可以找到一个匹配的集合(对于数字 1)。但是数字 1 至少不像数字 2 那样专业。如果我将 FTag 设置为除 ViewSpinLeaf 之外的任何值,那么数字 2 就无法匹配。因此,数字 2 更加专业。所以,我不明白为什么编译器不这么看。

作为第二次测试,我删除了专业化“A”(带有否定 enable_if 的那个)并从专业化“2”中删除了 enable_if_t 位。这编译得很好,这意味着数字“2”内的所有其他语句/类型定义都可以工作。但是,这不是我所需要的,因为此代码路径适用于所有 BinaryNode&lt;OpMultiply,..&gt; 而不仅仅是特定情况。

以防万一。我使用的编译器是 Ubuntu 上的 g++ 9.3,启用了标准 C++14。

编辑:正如评论中所建议的那样,BinaryNode&lt;Op,..&gt;BinaryNode&lt;OpMultiply,..&gt; 之间可能存在歧义。我将数字“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() );
}

我应该说基于EvalToSpinMatrix trait 选择带有enable_if 开关的部分特化很重要。显然,在实际应用中,这个特征更加复杂。很好,它在这个简单的版本中重现了歧义。

【问题讨论】:

  • 似乎歧义不在BinaryNode&lt;&gt; 之间,而是在BinaryNode&lt;Op, A, B&gt; 及其专业化BinaryNode&lt;OpMultiply, A, B&gt; 之间。您的 #1 与您的专业没有相同数量的参数。
  • @xryl669 有道理。我测试了一个在那个地方不应该有歧义的版本。尽管如此,编译器并不认为数字 2 更专业。
  • @ritter,对答案提供一些反馈怎么样?

标签: c++ template-meta-programming sfinae


【解决方案1】:

过了一段时间,我认为我有了答案。

首先,我将代码简化到最低限度,去掉了重现错误所需的任何内容。这一切都归结为为什么部分专业化在以下内容中模棱两可(我很抱歉更改了各个位的名称,但至少对我来说,消除你的代码并不是一件容易的事):

#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 并阅读所有内容:

非正式地,“A 比 B 更专业”的意思是“A 接受 B 接受的类型的子集”。

形式上,为了在偏特化之间建立更特化的关系,首先将每个偏特化转换为一个虚构的函数模板,如下所示:

  • 第一个函数模板与第一个部分特化具有相同的模板参数,并且只有一个函数参数,其类型是一个类模板特化,包含来自第一个部分特化的所有模板参数
  • [同上,但s/first/second/g]。

然后函数模板按照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>>>) {}

这是函数模板的两个不同的重载。因此,问题已通过上面第二个链接中描述的规则转移到他们中的哪一个更受欢迎。

老实说,此时我有点迷茫,所以我问a question,答案是即使第二个重载将std::pair的第一个模板参数固定为int,但第一个重载是将A 的第二个模板参数固定为void,因此没有一个比另一个更专业。而std::enable_if/std::enable_if_t 并没有改变这种情况,因为它被用作函数参数的类型(void,因为我们没有将第二个模板参数传递给std::enable_if/std::enable_if_t),不作为模板类型参数。

【讨论】:

  • 您的简化令人印象深刻 - 如此简短。这与您发布的新 SO 问题一起,我可能有解决方案 - 它尚未经过测试。在实施过程中。 C++ 概念似乎起到了作用。
  • @ritter,在您提出问题和我尝试回答之后,我开始阅读C++ Templates: The Complete Guide - 2nd edition,它在附录 C 中彻底处理了重载解决方案。
猜你喜欢
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
  • 2017-09-19
  • 2021-02-24
相关资源
最近更新 更多