【问题标题】:SFINAE and template function instantiation: Why a template argument cannot be deduced when used in function arguments with a SFINAE-enabled type?SFINAE 和模板函数实例化:为什么在启用 SFINAE 的类型的函数参数中使用模板参数时不能推导?
【发布时间】:2017-07-31 02:27:43
【问题描述】:

这些天我在试验 SFINAE,有些事情让我很困惑。为什么my_type_a不能在my_function的实例化中推导出来?

class my_type_a {};

template <typename T>
class my_common_type {
public:
    constexpr static const bool valid = false;
};

template <>
class my_common_type<my_type_a> {
public:
    constexpr static const bool valid = true;
    using type = my_type_a;
};

template <typename T> using my_common_type_t = typename my_common_type<T>::type;

template <typename T, typename V>
void my_function(my_common_type_t<T> my_cvalue, V my_value) {}

int main(void) {
    my_function(my_type_a(), 1.0);
}

G++ 给了我这个:

/home/flisboac/test-template-template-arg-subst.cpp: In function ‘int main()’:
/home/flisboac/test-template-template-arg-subst.cpp:21:30: error: no matching function for call to ‘my_function(my_type_a, double)’
  my_function(my_type_a(), 1.0);
                              ^
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note: candidate: template<class T, class V> void my_function(my_common_type_t<T>, V)
 void my_function(my_common_type_t<T> my_type, V my_value) {}
      ^~~~~~~~~~~
/home/flisboac/test-template-template-arg-subst.cpp:18:6: note:   template argument deduction/substitution failed:
/home/flisboac/test-template-template-arg-subst.cpp:21:30: note:   couldn't deduce template parameter ‘T’
  my_function(my_type_a(), 1.0);
                              ^

我所期望的是,当像在main 中那样调用my_function 时,T 将被推导出为函数的第一个参数的类型,并且该类型将用于函数的实例化。但似乎my_common_type_t&lt;T&gt; 在函数之前被实例化,但即便如此,my_cvalue 的类型无论如何都会变成my_type_a,所以我不明白为什么这不起作用......

有其他方法可以做到这一点吗?我应该避免两个(或更多)级别的模板间接吗?

【问题讨论】:

  • my_common_type&lt;T&gt;::type 中,Tnon-deduced context 中。您希望编译器用每种可能的类型T 实例化my_common_type,希望对于其中一个,my_common_type&lt;T&gt;::typemy_type_a 兼容;或者进行定理证明练习以尝试分析地找到这样的类型。编译器两者都不做。
  • @Igor 我了解规则 1(来自您提供的链接)当然与我的示例相匹配。但是,为什么不清楚T 不一定是my_type_a?如果在实例化 my_function 之前实例化了 my_common_type&lt;T&gt;,则类型将是 my_type_a 或什么都没有(因此该函数将通过 SFINAE 消除)。如果它在期间或之后被实例化,编译器会将my_common_type&lt;my_type_a&gt; 的信息作为候选(并且因此,T = my_type_a),不是吗?
  • “为什么不清楚” 这就是我所说的定理证明练习。这里的“清楚”是指“可以从现有的事实证明”。也许它可以——但编译器不需要拥有这种推理引擎。
  • 我认为可以从呼叫站点获得信息。但也许我从错误的角度考虑事情。我想我理解这里的问题,但我很难想出一个明确的答案。 my_function 在参数my_cvalue 中接收my_common_type&lt;T&gt;::type 类型的值,而T 不会在其他任何地方使用。我传递给函数的是my_type_a 的值,这是具体的。 my_common_type&lt;T&gt;::type 仍然未知,因为 ::type 依赖于模板替换,而模板替换又依赖于 T,目前尚不清楚。

标签: c++ templates c++14 template-argument-deduction


【解决方案1】:

好吧,考虑一下:

template <>
struct my_common_type<int> {
    constexpr static const bool valid = true;
    using type = my_type_a;
};

template <>
struct my_common_type<double> {
    constexpr static const bool valid = true;
    using type = my_type_a;
};

// ...

int main(void) {
    my_function(my_type_a{}, 1.0);
}

编译器是选择my_common_type&lt;int&gt;还是my_common_type&lt;double&gt;

如果语言允许在您的情况下进行推导,则它必须与 T 中的 my_common_type&lt;T&gt;::type 匹配,以便产生您发送给函数参数的确切类型。显然,这不仅不可能,而且以我上面的例子,它可能有多种选择!

幸运的是,有一种方法可以告诉编译器my_common_type&lt;T&gt; 将始终屈服于T。这个技巧的基本原理是这样的:

template<typename T>
using test_t = T;

template<typename T>
void call(test_t<T>) {}

int main() {
    call(1);
}

T 推导出什么? int,简单!编译器对这种匹配很满意。另外,由于test_t 不能被特化,所以test_t&lt;soxething&gt; 只知道是something

此外,这也适用于多级别名:

template<typename T>
using test_t = T;

template<typename T>
using test2_t = test_t<T>;

template<typename T>
void call(test2_t<T>) {}

int main() {
    call(1); // will also work
}

我们可以将此应用于您的案例,但我们需要一些工具:

template<typename T, typename...>
using first_t = T;

这与上面的简单匹配相同,但我们也可以发送一些不会使用的参数。我们将在这个未使用的包中制作 sfinae。

现在,重写 my_common_type_t 使其仍然是一个简单的匹配,同时在未使用的包中添加约束:

template <typename T>
using my_common_type_t = first_t<T, typename my_common_type<T>::type>;

请注意,这也有效:

template <typename T>
using my_common_type_t = first_t<T, std::enable_if_t<my_common_type<T>::valid>>;

现在扣除将按预期发生! Live (GCC) Live (Clang)

请注意,此技巧仅适用于 C++14,因为这种情况下的 sfinae(丢弃的参数)仅保证在 C++14 之后发生。

还请注意,您应该使用 struct 作为您的 trait,或者使用 public: 将成员 my_common_type&lt;T&gt;::type 公开,否则 GCC 将输出虚假错误。

【讨论】:

  • 哇,这当然是一个不错的选择,它回答了我的问题!但我的情况要复杂一些,因为 my_common_type&lt;T&gt;::type 是根据模板参数选择的。换句话说,这不仅仅是启用的问题。另外,我认为编译器会首先查看函数调用站点并看到Tmy_type_a,而不管my_common_type 的专业化。另外,我认为my_common_type&lt;int&gt;my_common_type&lt;double&gt; 都不会被选中,因为它们都不是my_common_type&lt;T&gt;T = my_type_a 的专业化。
  • 我想更新我的问题,但您的回答非常有用,我想再提出一个问题来回答我的具体问题。
  • @FlávioLisbôa 谢谢!总是乐于助人。在这个网站上提出另一个问题确实是正确的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多