【问题标题】:C++ template specialization - avoid redefinitionC++ 模板专业化 - 避免重新定义
【发布时间】:2017-05-30 08:37:53
【问题描述】:

我想要一个接受不同类型参数的通用函数(或方法)。如果提供的类型有“一个”方法,则函数应该使用它。如果它有 'two' 方法,函数应该使用它。

这是无效代码:

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

【问题讨论】:

  • @rene 不完全是重复的。如果他知道如何检查函数是否存在,他仍然不知道如何在这里使用检查器。显然他不知道如何使用那种检查器,否则他不会询问重新定义的问题
  • 您已经写了“改用它”。您是否打算使用两个,两者都存在?或者你想让它模棱两可?
  • 如果您打算测试的不仅仅是简单的功能,我在下面的答案中添加了一个非常通用的方法。

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


【解决方案1】:

是的,这是可能的。在 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. 我们对func_dispatch_tag 的第二个参数进行 SFINAE。编译器查看导致参数&lt;C, void&gt; 的所有模板特化。由于当 SF 不发生时(即std::enable_if_tvoid),后者中的任何一个都“更专业化”,因此它会被选中。

  2. 所选的特征特化定义了一个标签,我们在该标签上进行标签调度。标签调度依赖于函数重载,而不是函数模板特化(不能部分特化)。

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

  4. 该成员必须是可访问的,否则会发生 SF。此外,具有两个成员的类将导致歧义。请记住这一点。

【讨论】:

  • 加 1 是为了优雅地使用整数常量基类。减 1 表示没有实际检查方法(如果同时存在 1 和 2 则表示歧义)
  • @JohannesSchaub-litb - 实际上我正在筹备中。只是急于把它弄出来。
  • @JohannesSchaub-litb 你的意思是C::oneC::two 可以是任何成员,而不仅仅是一个方法?
  • @JohannesSchaub-litb - 虽然事实证明这种模棱两可很难解决
  • 如果不是这样写会不会很有趣,不是吗?哈哈
【解决方案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;
}

【讨论】:

    【解决方案3】:

    下面的代码

    • 正确处理成员函数常量
    • 与函数返回类型无关
    • 在失败时打印一个综合错误

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-23
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 2013-12-27
      • 2012-10-13
      • 1970-01-01
      相关资源
      最近更新 更多