【问题标题】:sfinae on function template instantiation函数模板实例化的 sfinae
【发布时间】:2020-05-20 02:28:56
【问题描述】:

我想检查是否可以为给定类型实例化函数模板。以模板为例:

template<typename T> void f() { T{}; }

我想断言f&lt;int&gt; 是可实例化的,而f&lt;S&gt; 不是,其中S 是某种类型,如果实例化会导致f 的定义编译失败,比如:

struct S { ~S() = delete; };

显然,如果我知道函数体包含什么,我可以为每个语句编写单独的检查。但是,如果身体本身是未知的,这种方法将不起作用。

看来我不能使用通常的 sfinae 方法来执行此操作,因为没有检查函数体是否有推导失败。这是要玩的attempt

确实,temp.deduct.8 似乎明确禁止这样做:

只有在函数类型、其模板参数类型及其显式说明符的直接上下文中的无效类型和表达式会导致推导失败。

“直接上下文”约束的原因似乎在下一个要点中:

注意:目的是避免要求实现处理涉及任意语句的替换失败。

如果我正确理解这一点,并且无法做到这一点,我能否解释一下为什么存在这个约束?我认为任意表达式可以被用户检查替换失败,那么为什么这对于实现来说太多了?

如果我对此有误解,并且可能的,我可以解决这个问题吗?

【问题讨论】:

  • 此约束存在的原因在您提出此问题之前立即引用的语句中进行了解释。而且,不,没有办法做到这一点,就像那样。 应该要做的是让模板函数本身,如果它的模板参数使用SFINAE来失败重载解析无法为特定类型实例化,可能类似于 template&lt;typename T, typename=std::void_t&lt;decltype(T{})&gt;&gt; void f()...
  • @SamVarshavchik 这需要我更改函数本身。事实上,这个问题假设我什至不知道定义的内容,否则我只会分别对定义中的各个语句进行处理,而不更改函数。这导致,为什么一个实现不能做到这一点?
  • 再一次,你引用的那句话已经解释了这一点。就是这样。这就是我们必须与之合作的。
  • 好的,但如果是这样,我只是在问为什么它需要那样?似乎是一个合理的问题。
  • @cigien:编译器使用函数声明:返回值和参数(以及模板化函数的模板参数)。在做出所有决定之前,它不会考虑函数的主体(此时,当“最佳”选择无法实例化时,您将收到编译器错误)

标签: c++ language-lawyer sfinae


【解决方案1】:

正如您在问题中所说,SFINAE 仅发生在“即时上下文”中,“演绎失败”包含的情况在[temp.deduct] 中列出。 您代码中发生的错误将是格式错误的,标准中提到了这一点:

对类型和表达式的替换可能会导致类模板特化和/或函数模板特化的实例化、隐式定义函数的生成等效果。这些效果不在“直接上下文”中,并且可以导致程序格式错误。

因此,当需要为类型 S 实例化函数模板 f 时,您的程序将不正确。

解决办法:

#include <iostream>
#include <type_traits>

template<typename T> void f() { T a{}; }

struct S { ~S() = delete; };
struct Test{};

template<typename T,typename U = void>
struct is_sutable_type_for_function:std::false_type{};

template<typename T>
struct is_sutable_type_for_function<T,std::void_t<decltype(T{}.~T())>>:std::true_type{};

int main()
{
   std::cout<< is_sutable_type_for_function<S>::value<<"\n";
   std::cout<< is_sutable_type_for_function<Test>::value<<"\n";
}

现在,代码将用于判断类型 T 是否可以用于函数模板f

【讨论】:

  • 这是一个巧妙的解耦(听起来像是 OP 想要的)。现在我们可以在调用函数之前在别处使用if constexpr。不过,我仍然更喜欢约束直接与函数一起出现(如我的回答)。
  • @AndyG yes,if constexpr是c++17发明的,可以方便使用代替c++17之前的SFINAE
【解决方案2】:

在 C++ 中,您必须对函数本身施加约束才能使检测起作用:

Demo (C++20)

#include <type_traits>

template<typename T> void f() requires std::is_constructible_v<T> { T{}; }

template<class T, class = void>
struct can_call_f : std::false_type{};

template<class T>
struct can_call_f<T, std::void_t<decltype(f<T>())>>: std::true_type{};

template<class T>
constexpr bool can_call_f_v = can_call_f<T>::value;

struct S { ~S() = delete; };
int main()
{
  static_assert(can_call_f_v<int>);
  static_assert(!can_call_f_v<S>);
}

回想一下,C++ 适用于类型,而函数体不被视为函数类型的一部分。

【讨论】:

  • 我知道这是目前的工作方式。是否有任何根本原因无法检查正文是否有效编译?还是只是难以实现?
  • 我不能说我在编译器实现方面的知识足够专业。我的猜测是它会让模板编译慢很多,已经是很大的瓶颈了。
  • 这很合理。也许检查函数体的明确请求是有意义的。那么人们只会为自己想要的东西付费。
猜你喜欢
  • 2016-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-08-16
  • 1970-01-01
相关资源
最近更新 更多