A better idiom for referring to base classes from

2019-07-13 13:07发布

问题:

Suppose I have a

template <typename T>
class A : 
    class_with_long_name<T, and_many_other, template_arguments, oh_my_thats_long>,
    anotherclass_with_long_name<and_many_other, template_arguments_that, are_also_annoying, including_also, T> { ... }

Now, in class A's definition, and/or in its methods, I need to refer to the two superclasses (e.g. to access members in the superclass, or types defined in it etc.) However, I want to avoid repeating the superclass names. At the moment, what I'm doing is something like:

template<typename T>
class A : 
    class_with_long_name<T, and_many_other, template_arguments, oh_my_thats_long>,
    anotherclass_with_long_name<and_many_other, template_arguments_that, are_also_annoying, including_also, T> 
{
    using parent1 = class_with_long_name<T, and_many_other, template_arguments, oh_my_thats_long>;
    using parent2 = anotherclass_with_long_name<and_many_other, template_arguments_that, are_also_annoying, including_also, T>;
    ...
 }

which works, obviously, and reduces the number of repetitions to 2; but I would rather avoid even this repetition, if possible. Is there a reasonable way to do this?

Notes:

  • "Reasonable" e.g. no macros except with very very good justification.

回答1:

If you inherit with the same access specifier for all classes you could use something like this:

template <typename...S>
struct Bases : public S... {
    template <size_t I>
    using super = typename std::tuple_element<I, std::tuple<S...>>::type;
};

This will give you access to all base classes in the order you inherit from them via super<index>.

Short example:

#include <iostream>
#include <tuple>


template <typename...S>
struct Bases : public S... {
    template <size_t I>
    using super = typename std::tuple_element<I, std::tuple<S...>>::type;
};


class Foo
{
public:
    virtual void f()
    {
        std::cout << "Foo";
    }
};

class Fii
{
public:
    virtual void f()
    {
        std::cout << "Fii";
    }
};

class Faa : private Bases<Foo, Fii>
{
public:
    virtual void f()
    {
        std::cout << "Faa";
        super<0>::f(); //Calls Foo::f()
        super<1>::f(); //Calls Fii::f()
        std::cout << std::endl;
    }
};



int main()
{
    Faa faa;
    faa.f(); //Print "FaaFooFii"
    return 0;
}


回答2:

Before A, you may do

namespace detail
{
    template <typename T>
    using parentA1 = class_with_long_name<T,
                                          and_many_other,
                                          template_arguments,
                                          oh_my_thats_long>;
    template <typename T>
    using parentA2 = anotherclass_with_long_name<and_many_other,
                                                 template_arguments_that,
                                                 are_also_annoying,
                                                 including_also,
                                                 T>;
}

And then

template<typename T>
class A : detail::parentA1<T>, detail::parentA2<T>
{
};


回答3:

I think, the best option is what you are already doing. However, if you feel like you absoluletely can not tolerate this, here some funny code (if I would ever see anything like this during code review in production, I'd fight tooth and nail to get rid of it).

template<class... INHERIT_FROM> 
struct inherit_publicly : public INHERIT_FROM... {
    struct parent_types {
        template <int N, class ARG, class... ARGS> struct get {
            using type = typename get<N-1, ARGS...>::type;
        };
        template <class ARG, class... ARGS> struct get<0, ARG, ARGS...> {
            using type = ARG;
        };
    };

    template <int N> using parent = typename parent_types::template get<N, INHERIT_FROM...>::type;

};

// **example usage** 

struct X { static constexpr const char* const name = "X"; };
struct Y { static constexpr const char* const name = "Y"; };
struct Z { static constexpr const char* const name = "Z"; };


#include <iostream>

struct A : inherit_publicly<X, Y, Z> {
    void print_parents() {
        std::cout << "First parent type: " << parent<0>::name << "; second: " << parent<1>::name << "; third: " <<parent<2>::name<< "\n";
    }
};

int main() {
    A a;
    a.print_parents();
}

Live demo: http://coliru.stacked-crooked.com/a/37cacf70bed41463



回答4:

You can just use A::class_with_long_name or A::anotherclass_with_long_name.

template<typename T>
class A 
    : class_with_long_name<T, and_many_other, template_arguments, oh_my_thats_long>
    , anotherclass_with_long_name<and_many_other, template_arguments_that, are_also_annoying, including_also, T> 
{
    // If you still want the typedefs.
    using parent1 = typename A::class_with_long_name;
    using parent2 = typename A::anotherclass_with_long_name;

    // If you don't.
    void foo() { A::class_with_long_name::bar(); }
};


回答5:

Based on @Jarod's solution: How about an inside-the-detail subnamespace?

namespace detail { namespace A {
    template <typename T>
    using parent1 = class_with_long_name<T,
                                          and_many_other,
                                          template_arguments,
                                          oh_my_thats_long>;
    template <typename T>
    using parent2 = anotherclass_with_long_name<and_many_other,
                                                 template_arguments_that,
                                                 are_also_annoying,
                                                 including_also,
                                                 T>;
} // namespace A
} // namespace detail

And then

template<typename T>
class A : detail::A::parent1<T>, detail::A::parent2<T>
{
};