c++ template specialization for all subclasses

后端 未结 5 585
隐瞒了意图╮
隐瞒了意图╮ 2020-12-30 09:27

I need to create a template function like this:

template
void foo(T a)
{
   if (T is a subclass of class Bar)
      do this
   else
      d         


        
相关标签:
5条回答
  • 2020-12-30 10:04

    I would use std::is_base_of along with local class as :

    #include <type_traits>  //you must include this: C++11 solution!
    
    template<typename T>
    void foo(T a)
    {
       struct local
       {
            static void do_work(T & a, std::true_type const &)
            {
                //T is derived from Bar
            }
            static void do_work(T & a, std::false_type const &)
            {
                //T is not derived from Bar
            }
       };
    
       local::do_work(a, std::is_base_of<Bar,T>());
    }
    

    Please note that std::is_base_of derives from std::integral_constant, so an object of former type can implicitly be converted into an object of latter type, which means std::is_base_of<Bar,T>() will convert into std::true_type or std::false_type depending upon the value of T. Also note that std::true_type and std::false_type are nothing but just typedefs, defined as:

    typedef integral_constant<bool, true>  true_type;
    typedef integral_constant<bool, false> false_type;
    
    0 讨论(0)
  • 2020-12-30 10:05

    I like this clear style:

    void foo_detail(T a, const std::true_type&)
    {
        //do sub-class thing
    }
    
    void foo_detail(T a, const std::false_type&)
    {
        //do else
    }
    
    void foo(T a)
    {
        foo_detail(a, std::is_base_of<Bar, T>::value);
    }
    
    0 讨论(0)
  • 2020-12-30 10:20

    You can do what you want but not how you are trying to do it! You can use std::enable_if together with std::is_base_of:

    #include <iostream>
    #include <utility>
    #include <type_traits>
    
    struct Bar { virtual ~Bar() {} };
    struct Foo: Bar {};
    struct Faz {};
    
    template <typename T>
    typename std::enable_if<std::is_base_of<Bar, T>::value>::type
    foo(char const* type, T) {
        std::cout << type << " is derived from Bar\n";
    }
    template <typename T>
    typename std::enable_if<!std::is_base_of<Bar, T>::value>::type
    foo(char const* type, T) {
        std::cout << type << " is NOT derived from Bar\n";
    }
    
    int main()
    {
        foo("Foo", Foo());
        foo("Faz", Faz());
    }
    

    Since this stuff gets more wide-spread, people have discussed having some sort of static if but so far it hasn't come into existance.

    Both std::enable_if and std::is_base_of (declared in <type_traits>) are new in C++2011. If you need to compile with a C++2003 compiler you can either use their implementation from Boost (you need to change the namespace to boost and include "boost/utility.hpp" and "boost/enable_if.hpp" instead of the respective standard headers). Alternatively, if you can't use Boost, both of these class template can be implemented quite easily.

    0 讨论(0)
  • 2020-12-30 10:22

    I know this question has been answered but nobody mentioned that std::enable_if can be used as a second template parameter like this:

    #include <type_traits>
    
    class A {};
    class B: public A {};
    
    template<class T, typename std::enable_if<std::is_base_of<A, T>::value, int>::type = 0>
    int foo(T t)
    {
        return 1;
    }
    
    0 讨论(0)
  • 2020-12-30 10:28

    The problem is that indeed you cannot do something like this in C++17:

    template<T>
    struct convert_t {
        static auto convert(T t) { /* err: no specialization */ }
    }
    template<T>
    struct convert_t<T> {
      // T should be subject to the constraint that it's a subclass of X
    }
    

    There are, however, two options to have the compiler select the correct method based on the class hierarchy involving tag dispatching and SFINAE.

    Let's start with tag dispatching. The key here is that tag chosen is a pointer type. If B inherits from A, an overload with A* is selected for a value of type B*:

    #include <iostream>
    #include <type_traits>
    
    struct type_to_convert {
        type_to_convert(int i) : i(i) {};
        type_to_convert(const type_to_convert&) = delete;
        type_to_convert(type_to_convert&&) = delete;
        int i;
    };
    
    struct X {
        X(int i) : i(i) {};
        X(const X &) = delete;
        X(X &&) = delete;
    public:
        int i;
    };
    struct Y : X {
        Y(int i) : X{i + 1} {}
    };
    struct A {};
    
    template<typename>
    static auto convert(const type_to_convert &t, int *) {
        return t.i;
    }
    template<typename U>
    static auto convert(const type_to_convert &t, X *) {
        return U{t.i}; // will instantiate either X or a subtype
    }
    
    template<typename>
    static auto convert(const type_to_convert &t, A *) {
        return 42;
    }
    
    template<typename T /* requested type, though not necessarily gotten */>
    static auto convert(const type_to_convert &t) {
        return convert<T>(t, static_cast<T*>(nullptr));
    }
    
    int main() {
        std::cout << convert<int>(type_to_convert{5}) << std::endl;
        std::cout << convert<X>(type_to_convert{6}).i << std::endl;
        std::cout << convert<Y>(type_to_convert{6}).i << std::endl;
        std::cout << convert<A>(type_to_convert{-1}) << std::endl;
        return 0;
    }
    

    Another option is to use SFINAE with enable_if. The key here is that while the snippet in the beginning of the question is invalid, this specialization isn't:

    template<T, typename = void>
    struct convert_t {
        static auto convert(T t) { /* err: no specialization */ }
    }
    template<T>
    struct convert_t<T, void> {
    }
    

    So our specializations can keep a fully generic first parameter as long we make sure only one of them is valid at any given point. For this, we need to fashion mutually exclusive conditions. Example:

    template<typename T /* requested type, though not necessarily gotten */,
             typename = void>
    struct convert_t {
        static auto convert(const type_to_convert &t) {
            static_assert(!sizeof(T), "no conversion");
        }
    };
    
    template<>
    struct convert_t<int> {
        static auto convert(const type_to_convert &t) {
            return t.i;
        }
    };
    
    template<typename T>
    struct convert_t<T, std::enable_if_t<std::is_base_of_v<X, T>>> {
        static auto convert(const type_to_convert &t) {
            return T{t.i}; // will instantiate either X or a subtype
        }
    };
    
    template<typename T>
    struct convert_t<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
        static auto convert(const type_to_convert &t) {
            return 42; // will instantiate either X or a subtype
        }
    };
    
    template<typename T>
    auto convert(const type_to_convert& t) {
        return convert_t<T>::convert(t);
    }
    

    Note: the specific example in the text of the question can be solved with constexpr, though:

    template<typename T>
    void foo(T a) {
       if constexpr(std::is_base_of_v<Bar, T>) 
          // do this
       else
          // do something else
    }
    
    0 讨论(0)
提交回复
热议问题