SFINAE模板特化优先级

16
#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, decltype(
  std::declval<const T&>().begin(),
  std::declval<const T&>().end(),
  void()
)> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

我原本期望第三个模板比第二个更为专业,但是我得到了一个"模板实例化不明确"的错误。
有没有办法让第三个模板更加专业?在第二个模板中显式地检查T是否为std::array对我来说行不通。我正在编写一个库,并希望用户能够定义自己的特殊化trait。第二个模板旨在成为缺少更具体trait时容器的通用特化。

请提供一个可重现的例子。 - Cheers and hth. - Alf
@SamVarshavchik 我试过了,但是没有改变任何东西。我正在使用gcc 7.2.0。我也尝试了gcc 8.1.0。 - SU3
Godbolt似乎同意:https://godbolt.org/z/OhJXm2 - SU3
1
相关:https://dev59.com/F1wZ5IYBdhLWcg3waPrX,可能由相同的原因引起。 - xskxzr
通常的解决办法是将SFINAE细节传递到不同的模板中。 - T.C.
显示剩余3条评论
1个回答

8
#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, std::void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>,void> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

编辑

首先,以下内容并非证明,只是我的猜想。也许其他人可以更正、扩展或复制粘贴它。

然而,看到这个问题后,我的第一个想法是使用std::void_t。我很确定我之前看到过类似的东西,但不能保证。 为了显示可以使用std::void_t,我们必须通过检查部分顺序来展示“一个模板特化比另一个更具体”。我将用下面的代码来模仿上面的内容,这样会更加简短。

template <typename T, typename SFINAE=void>
struct trait;
//#1
template <typename T>struct trait<T, std::void_t<decltype(std::declval<T>().begin())>>
{
  static const char* name() { return "Container"; }
};
//#2
template <typename T>struct trait<std::vector<T>,void> {
  static const char* name() { return "std::vector"; }
};

我不打算解释如何进行部分排序,这将太长时间。在转换为函数等之后,您最终会得到类似于以下内容的东西。

//#2 from #1: f(trait<std::vector<T>,void>) from f(trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>)
    //P=trait<std::vector<T>,void>
    //A=trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>>
        //P1=std::vector<T>
        //A1=__ANY_TYPE__
        //P2=void
        //A2=std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>
        //==> T=? --> fail, #2 from #1 is not working

现在我们需要展示#2中的#1是否起作用。如果确实如此,我们已经表明#2更加专业化。
//#1 from #2: f(trait<T, std::void_t<decltype(std::declval<T>().begin())>>) from f(trait<std::vector<__ANY_TYPE__>,void>)
    //P=trait<T, std::void_t<decltype(std::declval<T>().begin())>>
    //A=trait<std::vector<__ANY_TYPE__>,void>
        //P1=T
        //A1=std::vector<__ANY_TYPE__>
        //P2=std::void_t<decltype(std::declval<T>().begin())> //(*)
        //A2=void
        //==> T=std::vector<__ANY_TYPE__> ok #1 from #2 works

这基本上是我的草图,没有检查标准或其他任何东西。我相信你可以在标准的无尽行中找到它...

如果您注意到了(*),那么这一行基本上是唯一重要的,如果您想使用decltype(...)。我的猜测是,使用decltype(...)导致右侧的非推断上下文,这可能不允许使用P1 / A1推断中的T。但是,这基本上是我第一次没有包含答案的工作std::void_t解决方案的原因。 最后,替代的std::void_t定义与typename ...也是我认为像decltype(...)一样的非推断上下文,由于typename部分。


编辑

只是添加一些最终行。原则上,decltype sfinae不应该有问题。好吧,它是非推断上下文,但为什么会有问题?我能想到的唯一一件事是,非推断上下文在部分排序组合中具有一些特殊规则...


1
有趣的是,如果将void_t定义为template <typename...> using void_t = void;,则此代码可以正常工作,但如果定义为template<typename... Ts> struct make_void { typedef void type; }; template<typename... Ts> using void_t = typename make_void<Ts...>::type;则不行。 - SU3
我们最好等待其他人来回答这个问题。我太不确定了,不想传播无稽之谈。 - user8705939
@SU3,这种形式的模板参数make_void<Types...>::typedecltype(std::declval<T>()/*...*/)无法与模板偏特化配合使用,因为在模板类的部分排序过程中,这种形式的模板参数将成为无法推导的上下文无法推导的上下文包括:(5.1)使用限定符标识的类型的嵌套名称说明符。(5.2)decltype-specifier的表达式。 - Oliv
@Oliv 如果你把它写成一个答案,我会接受它。 - SU3
@Oliv 你说得对。因此,这种替代形式就像decltype(...)非推断上下文一样,与template <typename...> using void_t = void;相比,它不是非推断上下文。但为什么非推断上下文在这里是个问题呢?从这个意义上讲,我可以展示std::void_t是如何工作的,但我无法展示为什么decltype(...)不起作用。 - user8705939
@Su3,我有疑问! - Oliv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接