【问题标题】:How to detect if a function exists?如何检测函数是否存在?
【发布时间】:2019-09-28 01:11:32
【问题描述】:

我正在尝试检测我的函数的特定重载是否可调用。我以为我可以做类似于this answer 的事情,但我认为问题在于函数签名template<typename From, typename To> convert(const From&) 定义明确,但实例化不是。

#include <iostream>
#include <string>

template<typename From, typename To>
To convert(const From& from)
{
    // I have a lot of additional template specializations for this function
    return from; 
}

template<typename From, typename To>
struct IsConvertible
{
    template<typename = decltype(convert<From, To>(From()))>
    static std::true_type test(int);
    template<typename T>
    static std::false_type test(...);

    static bool const value = decltype(test(0))::value;
};

int main()
{
    std::cout << "IsConvertible=" << IsConvertible<int, float>::value << std::endl;
    // Returns 1 as expected

    std::cout << "IsConvertible=" << IsConvertible<int, std::string>::value << std::endl;
    // Returns 1, expected 0. The issue seems to be that decltype(convert<From, To>(From()))
    // is somehow ok, although convert<int, std::string>(1) definitly isn't
}

我想使用IsConvertible 进行一些额外的元编程。 是否可以检测到template&lt;typename From, typename To&gt; To convert(const From&amp;) 函数是否真的可以调用?`

【问题讨论】:

  • 没有。这被称为 SFINAE 不友好。
  • 我的 2 美分:我尝试了 coliru,因为我很难相信...Live Demo on coliru
  • 这不是超载,而是专业化。您可以检测是否存在重载,但您无法检测是否存在特化。使用重载。
  • 我不确定要详细说明什么。不要使用模板特化进行转换,使用重载。一个小问题是你不能重载返回类型,但这很容易用标签类型修复。

标签: c++ c++11 templates metaprogramming template-meta-programming


【解决方案1】:

我可能误解了您的问题,但在这种情况下使用 std::is_invocable 还不够,如下所示?

#include<type_traits>

template<typename From, typename To>
To convert(const From& from)
{
    // I have a lot of additional template specializations for this function
    return from; 
}

template<>
std::string convert(const int& from)
{
    //silly specialization
    return "2"+from; 
}


struct Foo{
    int bar;
};

int main()
{
   //ok specialization is called 
   std::cout<<std::is_invocable<decltype(convert<int,std::string>),std::string>::value<<std::endl; 
   //no way I can convert int to Foo, specialization required
   std::cout<<std::is_invocable<decltype(convert<int,Foo>),Foo>::value<<std::endl; 
return 0;
}

【讨论】:

  • 这个问题被标记为 C++11。
  • 我不知道 std::invocable,所以 +1。但正如前面的评论者所说,不幸的是我被 C++11 困住了。
  • @pingul 你可能想做的是研究invocable 是如何实现的,看看你是否可以做类似的事情。祝你好运。
【解决方案2】:

声明

template<typename From, typename To> To convert(const From& from);

你的特点

template<typename From, typename To>
struct IsConvertible

总是会检测到convert 函数的存在。

修复它的一种方法是重载和/或 SFINAE:

template <typename> struct Tag{};

int convertImpl(tag<int>, const std::string& from);
float convertImpl(tag<float>, const std::string& from);
// overloads ...

template<typename From, typename To>
auto convert(const From& from)
-> decltype(convertImpl(tag<To>{}, from))
{
    return convertImpl(tag<To>{}, from);
}

【讨论】:

  • 啊当然——这很有意义。现在我也明白了!
【解决方案3】:

我发现你的代码有一些问题。

没有特定的顺序...

(1) SFINAE,使用decltype(),只检查声明的函数是否存在;不检查该函数是否已定义或它的定义是否有效(编译)。

我建议你直接使用 SFINAE 重写convert()声明它只有在可编译时

template <typename To, typename From,
          decltype( To(std::declval<From>()), bool{} ) = true>
To convert (From const & f)
 { return f; }

只有当您可以从From 对象开始构造To 对象时,才会声明convert()

(2) 请注意,我还切换了ToFrom 的顺序:这样您就可以调用convert() 函数,只解释To 类型

convert<float>(0); // From is deduced as int from the 0 value

如果您在From(可推断)之后声明To(不可推断),则必须显式调用函数,同时在From 类型可推断时。

(3) 你的 IsConvertible 结构不起作用。

这是使用 SFINAE 的常见错误。

当你写作时

template<typename = decltype(convert<From, To>(From()))>
static std::true_type test(int);

您正在尝试使用 SFINAE 启用/禁用此 test() 方法,而不是 FromTo,它们是 struct

的模板参数

错了。

SFINAE 对方法本身的模板参数起作用。

如果你想使用SFINAE,你必须在方法的模板参数中转换FromTo;举例

template <typename F = From, typename T = To,
          typename = decltype(convert<F, T>(std::declval<F>()))>
static std::true_type test(int);

现在 SFINAE 使用 FT 作为 test() 方法的模板参数,这是正确的。

(4) 注意我写的是std::declval&lt;F&gt;() 而不是F()。这是因为您不确定 F (From) 是默认可构造的。使用std::declval() 可以解决这个问题。

我提出了一个不同的IsConvertible 自定义类型特征,它考虑了From/To 倒置和对value 调用test() 的需求From+To->@987654358 @+T类型转换

template <typename To, typename From>
struct IsConvertible
 {
   template <typename T, typename F,
             typename = decltype(convert<T>(std::declval<F>()))>
   static std::true_type test(int);

   template <typename...>
   static std::false_type test(...);

   static bool const value = decltype(test<To, From>(0))::value;
 };

(5) 你期待

IsConvertible<int, std::string>::value

为零;但是您忘记了std::string 可以从int 构造;所以这个值(或IsConvertible&lt;std::string, int&gt;,切换ToFrom)应该是1。

以下是更正后的完整工作示例

#include <iostream>
#include <string>
#include <vector>

template <typename To, typename From,
          decltype( To(std::declval<From>()), bool{} ) = true>
To convert (From const & f)
 { return f; }

template <typename To, typename From>
struct IsConvertible
 {
   template <typename T, typename F,
             typename = decltype(convert<T>(std::declval<F>()))>
   static std::true_type test(int);

   template <typename...>
   static std::false_type test(...);

   static bool const value = decltype(test<To, From>(0))::value;
 };

int main ()
 {
   std::cout << "IsConvertible=" << IsConvertible<float, int>::value
      << std::endl;

   std::cout << "IsConvertible=" << IsConvertible<int, std::string>::value
      << std::endl;
 }

【讨论】:

  • 这个例子中缺少的东西是我的convert 函数有许多非平凡可转换类型的特化/重载,因此添加decltype( To(std::declval&lt;From&gt;()), bool{} ) = true&gt; 作为条件将不起作用。但是,您的 #3 和 #4 是正确的,我已经将它与 Jarod 的答案一起使用。非常感谢!
猜你喜欢
  • 2014-02-18
  • 2015-07-24
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 2010-11-05
  • 2017-07-02
  • 1970-01-01
相关资源
最近更新 更多