如何检测函数是否存在?

How to detect if a function exists?

我正在尝试检测我的函数的特定重载是否可调用。我以为我可以做类似于 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<typename From, typename To> To convert(const From&) 函数是否实际可调用?`

我可能误解了你的问题,但在这种情况下使用 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;
}

声明

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

你的特质

template<typename From, typename To>
struct IsConvertible

将始终检测 convert 函数的存在。

修复它的一种方法是重载 and/or 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);
}

我发现您的代码中存在一些问题。

无特定顺序...

(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);

您正在尝试 enable/disable 这个 test() 方法使用 SFINAE 而不是 FromTo,它们是 struct[= 的模板参数100=]

错了。

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 使用 FTtest() 方法的模板参数,这是正确的。

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

我提出了一个不同的 IsConvertible 自定义类型特征,它考虑了对 test()value 调用的 From/To 反转和需求From+To->F+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<std::string, int>,切换ToFrom)应该是一个。

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

#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;
 }