C ++模板专业化 - 避免重新定义

时间:2017-05-30 08:37:54

标签: c++ templates template-specialization sfinae typetraits

我希望有一个接受不同类型参数的泛型函数(或方法)。如果提供的类型有一个'方法,该功能应该使用它。如果它有两个'方法,该函数应该使用它。

这里是无效代码:

#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. 如果两者都失败,则报告错误。

3 个答案:

答案 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;
}

重点:

  1. 我们SFINAE在func_dispatch_tag的第二个参数上。编译器会查看导致参数<C, void>的所有模板特化。因为后者中的任何一个都是更专业的&#34;当SF没有发生时(即当std::enable_if_tvoid时),它就会被选中。

  2. 所选特征的特性定义了一个标签,我们对其进行标签调度。标签分派取决于函数重载,而不是函数模板特化(不能部分专门化)。

  3. 您可以定义回退功能(就像我做的那样)或static_assert。我们可以定义的标签数量仅受int的范围限制,因此扩展到其他成员只需添加另一个func_dispatch_tag专业化。

  4. 该成员必须是可访问的,否则将发生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)();
}