我希望有一个接受不同类型参数的泛型函数(或方法)。如果提供的类型有一个'方法,该功能应该使用它。如果它有两个'方法,该函数应该使用它。
这里是无效代码:
#include <iostream>
template<typename Type> void func(Type t)
{
t.one();
}
template<typename Type> void func(Type t) // redefinition!
{
t.two();
}
class One
{
void one(void) const
{
std::cout << "one" << std::endl;
}
};
class Two
{
void two(void) const
{
std::cout << "two" << std::endl;
}
};
int main(int argc, char* argv[])
{
func(One()); // should print "one"
func(Two()); // should print "two"
return 0;
}
是否可以使用SFINAE?是否可以使用type_traits实现?
澄清:
如果可以使用SFINAE,我会更高兴。最好的情况是:使用第一个模板,如果失败则使用第二个模板。
检查方法存在只是一个例子。我真正想要的还是检查与其他类的兼容性。
任务可以改写:
答案 0 :(得分:5)
是的,这是可能的。在C ++ 11中,它甚至相对容易。
#include <iostream>
#include <type_traits>
template<class, typename = void>
struct func_dispatch_tag :
std::integral_constant<int, 0> {};
template<class C>
struct func_dispatch_tag<C,
std::enable_if_t<std::is_same<decltype(&C::one), void (C::*)() const>::value>
> : std::integral_constant<int, 1> {};
template<class C>
struct func_dispatch_tag<C,
std::enable_if_t<std::is_same<decltype(&C::two), void (C::*)() const>::value>
> : std::integral_constant<int, 2> {};
template<class C>
void func(C const&, std::integral_constant<int, 0>) {
std::cout << "fallback!\n";
}
template<class C>
void func(C const &c, std::integral_constant<int, 1>) {
c.one();
}
template<class C>
void func(C const &c, std::integral_constant<int, 2>) {
c.two();
}
template<class C>
void func(C const &c) {
func(c, func_dispatch_tag<C>{});
}
struct One
{
void one(void) const
{
std::cout << "one\n";
}
};
struct Two
{
void two(void) const
{
std::cout << "two\n";
}
};
struct Three {};
int main(int argc, char* argv[])
{
func(One()); // should print "one"
func(Two()); // should print "two"
func(Three());
return 0;
}
重点:
我们SFINAE在func_dispatch_tag
的第二个参数上。编译器会查看导致参数<C, void>
的所有模板特化。因为后者中的任何一个都是更专业的&#34;当SF没有发生时(即当std::enable_if_t
为void
时),它就会被选中。
所选特征的特性定义了一个标签,我们对其进行标签调度。标签分派取决于函数重载,而不是函数模板特化(不能部分专门化)。
您可以定义回退功能(就像我做的那样)或static_assert
。我们可以定义的标签数量仅受int的范围限制,因此扩展到其他成员只需添加另一个func_dispatch_tag
专业化。
该成员必须是可访问的,否则将发生SF。此外,具有两个成员的类将导致歧义。记住这一点。
答案 1 :(得分:2)
这是另一种方式。还有更多的样板,但在func()
的不同实现的实际表达中,可以认为通过&#39;的测试列表。表现力更强。
代码是c ++ 11。 c ++ 14和17会更简洁。
#include <iostream>
#include <type_traits>
#include <tuple>
// boilerplate required prior to c++17
namespace notstd {
using namespace std;
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
}
// test for having member function one()
template<class T, class Enable = notstd::void_t<>> struct has_one : std::false_type {};
template<class T> struct has_one<T, notstd::void_t<decltype(std::declval<T>().one())>> : std::true_type {};
//test for having member function two()
template<class T, class Enable = notstd::void_t<>> struct has_two : std::false_type {};
template<class T> struct has_two<T, notstd::void_t<decltype(std::declval<T>().two())>> : std::true_type {};
// a type collection of tests that pass
template<template <class...> class...Tests> struct passes_tests {
};
// meta-function to append a type
template<class Existing, template <class...> class Additional> struct append_pass;
template< template <class...> class...Tests, template <class...> class Additional>
struct append_pass<passes_tests<Tests...>, Additional> {
using type = passes_tests<Tests..., Additional>;
};
//
// meta-functions to compute a list of types of test that pass
//
namespace detail
{
template<class Previous, class T, template<class...> class Test, template<class...> class...Rest>
struct which_tests_pass_impl
{
using on_pass = typename append_pass<Previous, Test>::type;
using on_fail = Previous;
using this_term = typename std::conditional< Test<T>::value, on_pass, on_fail >::type;
using type = typename which_tests_pass_impl<this_term, T, Rest...>::type;
};
template<class Previous, class T, template<class...> class Test>
struct which_tests_pass_impl<Previous, T, Test>
{
using on_pass = typename append_pass<Previous, Test>::type;
using on_fail = Previous;
using this_term = typename std::conditional< Test<T>::value, on_pass, on_fail >::type;
using type = this_term;
};
}
template<class Type, template<class...> class...Tests> struct which_tests_pass
{
using type = typename detail::which_tests_pass_impl<passes_tests<>, Type, Tests...>::type;
};
//
// various implementations of func()
//
namespace detail
{
template<class T>
void func(T t, passes_tests<has_one>)
{
t.one();
}
template<class T>
void func(T t, passes_tests<has_one, has_two>)
{
t.one();
}
template<class T>
void func(T t, passes_tests<has_two>)
{
t.two();
}
template<class T>
void func(T t, passes_tests<>)
{
// do nothing
}
}
template<class T>
void func(T t)
{
detail::func(t, typename which_tests_pass<T, has_one, has_two>::type());
}
//
// some types
//
struct One
{
void one(void) const
{
std::cout << "one" << std::endl;
}
};
struct Two
{
void two(void) const
{
std::cout << "two" << std::endl;
}
};
// test
int main(int argc, char* argv[])
{
func(One()); // should print "one"
func(Two()); // should print "two"
return 0;
}
答案 2 :(得分:0)
以下代码
使用C ++ 14可能更短,您不必指定已实现函数的返回类型并具有模板化变量声明。如果要正确处理rvalue重载,则需要为as_memfun
提供另一个重载。
如果单独测试成员函数是不够的,那么在上一节中还有另一种方法,它提供了更好的自定义选项,但设置起来也更长。
#include <utility>
#include <functional>
namespace detail {
template<typename T> struct _false : std::integral_constant<bool, false> { };
template<typename T> struct HasNone {
static_assert(_false<T>::value, "No valid method found");
};
template<typename T, typename R>
constexpr auto as_memfun (R (T::* arg) ())
-> R (T::*) ()
{ return arg; }
template<typename T, typename R>
constexpr auto as_memfun (R (T::* arg) () const)
-> R (T::*) () const
{ return arg; }
template<typename T> constexpr auto check_has_two(int)
-> decltype(as_memfun(&T::two))
{ return as_memfun(&T::two); }
template<typename T> constexpr auto check_has_two(...)
-> HasNone<T>;
template<typename T> constexpr auto check_has_one(int)
-> decltype(as_memfun(&T::one))
{ return as_memfun(&T::one); }
template<typename T> constexpr auto check_has_one(...)
-> decltype(check_has_two<T>(0))
{ return check_has_two<T>(0); }
template<typename T>
struct res { constexpr static auto detail = check_has_one<T>(0); };
}
template<typename T>
auto func(T t) -> decltype((t.*detail::res<T>::detail)()) {
return (t.*detail::res<T>::detail)();
}
这里有一些你可能想要的测试
struct One {
void one();
};
struct Two {
void two();
};
struct TestBoth {
char one() const;
void two();
};
struct TestWilderStuff {
int one;
void two() const;
};
int main() {
func(One{});
func(Two{});
func(TestBoth{});
static_assert(decltype(func(TestBoth{})){} == 0, "Failed function selection");
func(TestWilderStuff{});
}
由于您似乎有更多的构造,而不仅仅是测试成员函数的存在,这里是一个更强大的机制的开始。您可以将它作为上述解决方案的替代品,虽然它更长,但它提供了更多的自定义功能,并且可以在每一步中对您的类型进行精心测试。
#include <utility>
#include <functional>
namespace detail {
template<typename T> struct _false :
std::integral_constant<bool, false> { };
template<typename T> struct HasNone {
static_assert(_false<T>::value, "No valid method found");
};
// Generic meta templates used below
namespace Generics {
template<typename Getter, typename Else>
struct ChainGetter {
template<typename T> constexpr static auto get_(int)
-> decltype(Getter::template get<T>())
{ return Getter::template get<T>(); }
template<typename T> constexpr static auto get_(...)
-> decltype(Else::template get<T>())
{ return Else::template get<T>(); }
template<typename T> constexpr static auto get()
-> decltype(get_<T>(0))
{ return get_<T>(0); }
};
template<typename Getter, typename Test>
struct TestGetter {
template<typename T, typename R> using _type = R;
template<typename T> constexpr static auto get_()
-> decltype(Getter::template get<T>())
{ return Getter::template get<T>(); }
template<typename T> constexpr static auto test()
-> decltype(Test::template test<T>(get_<T>()));
template<typename T> constexpr static auto get()
-> _type<decltype(test<T>()),
decltype(get_<T>())
>
{ return get_<T>(); }
};
template<template<typename> class F>
struct FailGetter {
template<typename T>
constexpr static auto get() -> F<T>;
};
}
// Test only exists for member function pointer arguments
struct IsMemberFunctionTest {
template<typename _, typename T, typename R>
constexpr static void test (R (T::* arg) ());
template<typename _, typename T, typename R>
constexpr static void test (R (T::* arg) () const);
};
// Get member pointer to T::one
struct GetOne {
template<typename T>
constexpr static auto get() -> decltype(&T::one) { return &T::one; }
};
// Get member pointer to T::two
struct GetTwo {
template<typename T>
constexpr static auto get() -> decltype(&T::two) { return &T::two; }
};
using namespace Generics;
using getter_fail = FailGetter<HasNone>;
using get_two_tested = TestGetter<GetTwo, IsMemberFunctionTest>;
using getter_two = ChainGetter<get_two_tested, getter_fail>;
using get_one_tested = TestGetter<GetOne, IsMemberFunctionTest>;
using getter_one = ChainGetter<get_one_tested, getter_two>;
template<typename T>
struct result { constexpr static auto value = getter_one::template get<T>(); };
}
template<typename T>
auto func(T t) -> decltype((t.*detail::result<T>::value)()) {
return (t.*detail::result<T>::value)();
}