2

So I have a tremendous number of template specializations of this template:

template <typename T> // Same
struct foo { // Same
    using type_name = T; // Same
    foo(const int base) : _base(base) {} // May take other parameters
    void func(const T& param) {} // This function signature will be the same but body will differ
    int _base; // Same but may have more members
}; // Same

So an example specialization would be:

template<>
struct foo<float> {
    using type_name = T;
    foo(const int base, const int child) : _base(base), _child(child) {}
    void func(const T& param) { cout << param * _child << endl; }
    int _base;
    int _child;
};

Obviously this is a toy example and the body of _func will be more involved. But I think this expresses the idea. I can obviously make a macro to help with the boilerplate and put the implementation of the specialized version of the function in an implementation file.

But I was hoping that C++ provided me a way to do this without macros. Is there another way for me avoid writing the boilerplate over and over?

Ron
  • 14,674
  • 4
  • 34
  • 47
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • `// This function signature will be the same but body will differ` makes me think of inheritance rather than templates. – Yksisarvinen Jul 19 '18 at 14:42
  • 5
    There's very little here that's actually boilerplate. You could define `template struct foo_base { using type_name = T; int _base; };` and derive `foo` specializations from that - that'll save you two lines per specialization. – Igor Tandetnik Jul 19 '18 at 14:42
  • @IgorTandetnik Ugh, I think you're right :( There must not be anything better T.T – Jonathan Mee Jul 19 '18 at 14:47
  • @Ron I guess I've Just always thought of them as titles. – Jonathan Mee Jul 19 '18 at 14:58

4 Answers4

3

you can have multiple specialization for the function but not for the whole class

like this

#include <iostream>
#include <string>

template<typename T>
struct foo {
    //common generic code
    using type_name = T;
    foo(const int base, const int child) : _base(base), _child(child) {}
    void func(const T& param);
    int _base;
    int _child;
};

template<>
void foo<float>::func(const type_name&) {
    //implementation
    std::cout << "float" << std::endl;
}

template<>
void foo<int>::func(const type_name&) {
    //implementation
    std::cout << "int" << std::endl;
}


int main() {
    foo<int> tint(0, 0);
    foo<float> fint(0, 0);

    tint.func(0);
    fint.func(0);
}
Tyker
  • 2,971
  • 9
  • 21
  • Wow... I didn't know that you could do this... I'm going to have to explore this a bit further. – Jonathan Mee Jul 19 '18 at 14:57
  • @JonathanMee And I think you can factor out the constructor and member stuff by putting it in private CRTP base class specializations and inheriting the constructors. – Tim Seguine Jul 19 '18 at 15:08
  • @TimSeguine Let's say that I could figure this out... how would I solve the constructor problem? – Jonathan Mee Jul 19 '18 at 16:29
  • @JonathanMee I wrote that directly before leaving my office for the day. When I get a chance I will type up a proper explanation with less cryptic jargon as an answer. – Tim Seguine Jul 20 '18 at 09:49
1

You can use some light inheritance of data structs to help you separate the differences in member layout and constructor definitions from the main template.

//Define an internal aggregate type you can specialize for your various template parameters
template <typename T>
struct foo_data {
    foo(const int base) : _base(base) {}
    int _base;
};

//Then derive privately from the data struct (or publicly if you really desire)
template <typename T>
struct foo : private foo_data<T> {
    using type_name = T;
    using foo_data<T>::foo_data<T>; //Make the base class constructors visible
    void func(const T& param); //Use member specialization as suggested by the other answer
};

I will leave it to you to decide if it is better this way or not, but the upshot is that all the common parts are completely separated from all the uncommon parts.

In a comment under another answer I erroneously described this as CRTP. It isn't and it doesn't have any of the drawbacks as CRTP.

If you really need to preserve standard layout, then you can simulate inheritance manually with explicit delegation and perfect forwarding.

template <typename T>
struct foo {
    using type_name = T;
    template <typename... Args>
    foo(Args&&... args) : base_data_(std::forward<Args>(args)...) {}
    void func(const T& param); //Use member specialization as suggested by the other answer
    foo_data<T> base_data_; 
};

One drawback is I don't think the delegating constructor will SFINAE properly as written, and it also eats noexcept specifiers and explicit. Fixing those issues(if required) is left as an exercise to the reader.

Tim Seguine
  • 2,887
  • 25
  • 38
  • Yeah when I started trying to code up a CRTP solution I found that it didn't solve anything, but rather added to the complexity. This solution with the templated constructor is actually a pretty good one for my needs. I don't know why I hadn't thought of it sooner. There's still movement on the question, but I'll probably accept this when things cool down. – Jonathan Mee Jul 20 '18 at 12:44
  • Arg... found the hitch. This one doesn't allow me to declare multiple member variables to correspond to the template arguments :( I need to work with this a bit further... Maybe I could roll this all into the object's template type... – Jonathan Mee Jul 20 '18 at 12:51
  • I am having trouble figuring out your last comment. Depending on what you meant, I think you can tweak this solution to do that. The biggest problem with doing this type of thing in C++ is that it is not very well supported and the syntax dance to do anything specific is not obvious. What you seem to really want is mixins. For now that means CRTP or some other template black magic, but I have high hopes that http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0847r0.html will make a lot of our mixin envy less painful. – Tim Seguine Jul 20 '18 at 14:58
1

There is no nice way to avoid some redundancy in notation when implementing specializations of templated types. There are some techniques to avoid duplication of actual code, such as

  1. Using a traits template to provide type-specific things

    template<typename T>
    struct foo_traits { ... };  // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo
    {
        using traits = foo_traits<T>;
    
        template<typename...Aars>
        explicit foo(Args&&...args)
         : data(std::forward<Args>(args)...) {}
    
        int do_something_specific(T x)
        { return traits::do_something(data,x); }
      private:
        typename traits::data data;
    };
    
  2. a very similar approach is to use a specialized base class:

    template<typename T>
    struct foo_base { ... };    // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo : foo_base<T>
    {
        using base = foo_base<T>;
    
        template<typename...Aars>
        explicit foo(int m, Args&&...args)
         : base(std::forward<Args>(args)...)
         , more_data(m) {}
    
        int do_something_specific(T x)
        { return base::do_something(x,more_data); }
      private:
        int more_data;
    };
    

    The constructor of foo is a variadic template in order to allow the base class's constructor to take any number and type of arguments.

  3. Of you can use a common base class and specialize the derived classes. This can be done with the Curiously recurring template pattern (CRTP)

    template<typename Derived>
    struct foo_base            // no specializations
    {
        using type = typename Derived::type;
        int do_something(type x)
        {
            auto result = static_cast<Derived*>(this)->specific_method(x);
            return do_some_common_stuff(result);
        }
      protected:
        foo_base(type x) : data(x) {}
        type data;
      private:
        int do_some_common_stuff(type x)
        { /* ... */ }
    };
    
    template<typename T>       // some specialisations
    struct foo : foo_base<foo<T>>
    {
        using base = foo_base<foo>;
        using type = T;
        using common_type = typename base::common_type;
        using base::do_something;
        explicit foo(type x, type y)
          : base(x), extra_data(y) {}
      protected:
        type specific_method(type x)
        { /* ... */ }
      private:
        type extra_data;
    };
    

    Note that foo_base is already a template (unlike the situation with ordinary polymorphism), so you can do a lot of specific stuff there already. Only things that are done differently (not merely with different types) need specializations of foo.

  4. Finally, you can combine these approaches, for example traits classes with CRTP.

All these methods implement some type of static or compile-time polymorphism, rather than real or dynamic polymorphism: there are no virtual functions and hence no virtual table and no overhead for table look-up. It is all resolved at compile time.

Walter
  • 44,150
  • 20
  • 113
  • 196
0

This is usually done through inheritance - you put the immutable part into base class, and specialize the children.

I do not think you need an example for that, but let me know if you do.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • I mean, I tried inheritance first, but I just have to rewrite the constructor, so I've just saved myself the declaration of the `int base` :( – Jonathan Mee Jul 19 '18 at 14:45
  • @JonathanMee can you show an example which is closer to your actual code? As commented above, there is almost no boilerplate in your code, so your specialization seems fine. – SergeyA Jul 19 '18 at 14:46
  • @JonathanMee As Igor mentioned, you have so little duplication in your specialization that any attempt at factorizing it will likely create more boilerplate than remove any. – Holt Jul 19 '18 at 14:47