2017-05-30 49 views
4

我想拥有一个接受不同类型参数的泛型函数(或方法)。如果提供的类型具有'one'方法,则该函数应该使用它。如果它有'两个'方法,该函数应该使用它。C++模板专业化 - 避免重新定义

这里是无效的代码:

#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。最好的情况是:使用第一个模板,如果失败,则使用第二个模板。

检查方法的存在只是一个例子。我真正想要的是也检查与其他类的兼容性。

任务可以改写:

  1. 如果该类支持第一接口,使用它。
  2. 如果第一个接口失败,请使用第二个接口。
  3. 如果两者均失败,则报告错误。
+1

的https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence – Rene

+0

可能重复的可能的复制[是否可以编写一个模板来检查函数的存在?](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a -functions-existence) –

+0

@rene不完全是重复的。如果他知道如何检查功能存在,他仍然不知道如何在这里使用检查器。显然他不知道如何使用那种跳棋,否则他不会问关于重新定义 –

回答

4

是的,这是可能的。在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; 
} 

要点:

  1. 我们SFINAE上的func_dispatch_tag第二个参数。编译器查看所有导致参数为<C, void>的模板特化。由于后者中的任何一个在SF不发生时(即当std::enable_if_tvoid时)是“更专业的”,所以它被选择。

  2. 特质的选定专业化定义了一个标签,我们做标签调度。标签调度取决于函数重载,而不是函数模板专门化(不能部分专用)。

  3. 您可以定义后备功能(如我所做的)或static_assert。我们可以定义的标签数量仅受限于int的范围,因此扩展到其他成员只是增加另一个func_dispatch_tag专业化的问题。

  4. 成员必须是可访问的,否则将发生SF。另外,一个有两个成员的类都会导致模糊。牢记这一点。

+2

使用积分常量基类为优雅加1。减1,因为没有真正检查一种方法(如果存在一个和两个都存在歧义) –

+0

@ JohannesSchaub-litb - 我实际上已经在管道中了。只是急于解决这个问题。 – StoryTeller

+0

@ JohannesSchaub-litb你的意思是'C :: one'和'C :: two'可以是任何成员而不仅仅是一个方法? –

2

这是另一种方法。还有一些更多的样板,但是在func()的不同实现的实际表达中,可以认为'通过的测试列表'更具表现力。无论如何,无论如何,我们都需要思考。

代码是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; 
} 
0

下面

  • 的代码处理成员函数常量性正确
  • 是不可知的函数返回类型
  • 版画的综合误差失败

它可能会更使用C++ 14更短,您不必指定实现函数和hav的返回类型e模板变量声明。如果要正确处理右值过载,则需要为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)(); 
}