2016-11-29 63 views
6

我想实现一个C++模板元函数,确定是否可从方法输入参数调用类型。C++元函数,确定是否可供调用提供的参数类型

即一个函数void foo(double, double)元函数会为callable_t<foo, int, int>(由于编译器做隐式转换)和false为别的返回truecallable_t<foo, double, double>true如错误的参数个数callable_t<foo, double>

我的尝试如下,但它失败的任何函数返回除void以外的任何东西,我似乎无法修复它。

我是新来的模板重新编程,所以任何帮助,将不胜感激。

#include <iostream> 
#include <type_traits> 
#include <utility> 
#include <functional> 

namespace impl 
{ 

template <typename...> 
struct callable_args 
{ 
}; 

template <class F, class Args, class = void> 
struct callable : std::false_type 
{ 
}; 

template <class F, class... Args> 
struct callable<F, callable_args<Args...>, std::result_of_t<F(Args...)>> : std::true_type 
{ 
}; 

} 

template <class F, class... Args> 
struct callable : impl::callable<F, impl::callable_args<Args...>> 
{ 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, Args...>::value; 


int main() 
{ 
    { 
     using Func = std::function<void()>; 
     auto result = callable_v<Func>; 
     std::cout << "test 1 (should be 1) = " << result << std::endl; 
    } 

    { 
     using Func = std::function<void(int)>; 
     auto result = callable_v<Func, int>; 
     std::cout << "test 2 (should be 1) = " << result << std::endl; 
    } 

    { 
     using Func = std::function<int(int)>; 
     auto result = callable_v<Func, int>; 
     std::cout << "test 3 (should be 1) = " << result << std::endl; 
    } 

    std::getchar(); 

    return EXIT_SUCCESS; 
} 

我使用的是支持C++ 14

+0

怎么样'callable_t '? – NathanOliver

+0

@NathanOliver,编译器推断的任何参数都可以被调用(虽然有警告)应该是有效的,所以如果foo被定义为foo(int,int),那么'callable_t int)'或'foo(double,double)'或'foo(float,float)'而不是'foo(custom_type,custom_type)',其中'custom_type'不能被隐含转换。 – keith

+0

@Marco答:可悲的是:-( – keith

回答

2

的缩短使用std::result_of做你想做的可能是什么如下:

template <class T, class, class... Args> 
struct callable: std::false_type { 
}; 

template <class T, class... Args> 
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type { 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

你必须记住,类型由result_of返回永远是你传递一个函数的结果类型通过类型来表达这种特质。为了让你的sfinae工作,你需要一种方法来改变这种类型在任何可能的情况下都是无效的。您可以使用decltype的技巧(decltype(std::result_of_t<T(Args...)>(), void()))来完成它。

编辑:

要了解该解决方案的可能弊端意见阐述的主题。 std::result_of_t<T(Args...)>类型不需要配备默认非参数构造函数,因此sfinae可能会导致产生此类型函数的错误否定结果callable_v。在我的意见提出了问题的解决方法并没有真正解决问题或实际产生一个新问题:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void()) 

这段代码的意图是使SFINAE工作在先前提出的解决方案,但在非情况可构造类型来创建一个易于构造(我认为)指向给定类型的指针的对象...在这个推理中,我没有考虑到不能创建指向例如引用。

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void()) 

或腐烂的结果类型:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void()) 

,但我想可能不值得,也许使用void_t的其实是这其中又可以通过使用一些额外的包装类是解决方法一个更简单的解决方案:

template <class...> 
struct voider { 
    using type = void; 
}; 

template <class... Args> 
using void_t = typename voider<Args...>::type; 

template <class T, class, class... Args> 
struct callable: std::false_type { 
}; 

template <class T, class... Args> 
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type { 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

+0

我喜欢这个。我确实尝试做类似但失败。很好的例子(我赞成你):-)在你的例子中,我可以使用void_t而不是你的decltype技巧,但了解decltype技巧是非常有用的 - 谢谢! – keith

+0

@keith我比'void_t'更喜欢'decltype',因为它允许你进行自定义调整......例如,你可以将任何类型改为int,如果你愿意的话 - 你注定不会将所有东西都改为void ...更多你不需要实现任何东西,因为你在C++ 11开箱即可获得'decltype' –

+0

有没有任何情况下这不起作用?它非常简单和优雅。这里有一个类似的版本:http://talesofcpp.fusionfenix。com/post-11/true-story-call-me-maybe – keith

2

以下是我想接近这个编译器:

namespace detail { 

template<typename Func, typename...Params> static auto helper(int) -> 
    decltype((void)std::declval<Func>()(std::declval<Params>()...), std::true_type{}); 

template<typename Func, typename...Params> static std::false_type helper(...); 

} 

template<typename Func, typename... Params> struct callable: 
    decltype(detail::helper<Func, Params...>(0)){}; 

template <class F, class... Args> constexpr auto callable_v = 
    callable<F, Args...>::value; 

demo

这是一个穷人的版本的C++ 1z的is_callable,但它不处理指向成员的指针。除此之外,我认为这很好。

+0

谢谢,我已经给你一个upvote,因为它比我的代码非常有趣和简单它并不能帮助我理解如何修复我的代码尽管:-) – keith

2

与您的原始代码的问题是,你正在使用的参数包在非抵扣范围内

namespace impl 
{ 

    template <class F, class... Args> 
    struct callable : std::false_type 
    { 
    }; 

    template <class F, class... Args> 
    struct callable<F, std::result_of_t<F(Args...)>> : std::true_type 
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
    { 
    }; 

} 

在源代码解析有这点可能是没有办法,std::result_of_t<Func, int>可以产生另一个返回值,但有可能是文件中的另一专业化以后,如以下(很变态)片断

namespace std { 

    template <> 
    struct result_of<Func(int)> { 
     using type = double; 
    }; 
} 

因此你的编译器应该能够拿起正确的人之前在同一时间检查所有的人。

这也是为什么像你的情况

template<class...> using void_t = void; 

namespace impl 
{ 

    template <typename...> 
    struct callable_args 
    { 
    }; 

    template <class F, class Args, class = void> 
    struct callable : std::false_type 
    { 
    }; 

    template <class F, class... Args> 
    struct callable<F, callable_args<Args...>, void_t<std::result_of_t<F(Args...)>>> : std::true_type 
    { 
    }; 

} 

工作变通的理由:他们帮助编译器解决依赖型的东西,总是解析为void。请记住,上面的代码是解决方法,您应该使用is_callable(C++ 17)或研究如何实现is_callable并深入了解其技术挑战。

+0

等一下,你确定这里的非推导上下文是潜在的原因,而不是模板专门化模式匹配失败吗?我不相信问题可以阅读,因为你建议... –

+0

感谢您的信息,我已经给你一个感谢的upvote。我将寻找编译器供应商的is_callable实现来了解它的工作原理。 – keith