Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specialize member functions based on size of member container

I have a class that holds some statically-sized containers:

template <typename Container>
struct Point {
    Container container;
    ... 
    void bar();
}

where a Container class might look like:

struct Container1 {
    static constexpr size_t size = 5;
}

Now I want to specialize the bar method based on the size of the container. I do not understand how to do that.

EDIT:

I would like a C++11 solution. C++14 might work, but the compilers we work with typically have spotty C++14 support.

EDIT:

Stack Danny suggested a solution which compiles with Clang, but not with GCC.

like image 499
bremen_matt Avatar asked Dec 18 '22 18:12

bremen_matt


2 Answers

Instead of specialisation, use SFINAE

template <typename Container>
class Point {
    Container container;
    template<size_t S> std::enable_if_t<S==3>
    bar_t() { std::cout << "specialisation for size=3\n"; }
    template<size_t S> std::enable_if_t<S==5>
    bar_t() { std::cout << "specialisation for size=5\n"; }
    template<size_t S> std::enable_if_t<S==42>
    bar_t() { std::cout << "specialisation for size=42\n"; }
  public:
    void bar()
    { bar_t<Container::size>(); }
};

std::enable_if_t is C++14, but you can trivially declare it yourself:

#if __cplusplus < 201402L
template<bool C, typename T=void>
using enable_if_t = typename enable_if<C,T>::type;
#endif

Btw, your question smells like an XY problem: do you really need to specialize bar() for the Container::size? In the following example, a loop is unrolled for any size N.

template<typename scalar, size_t N>
class point // a point in R^N
{
    scalar array[N];
  public:
    // multiplication with scalar
    point& operator*=(scalar x) noexcept
    {
        // unroll loop using template meta programming
        loop([array,x](size_t i) { array[i] *= x; };);
        /* alternatively: rely on the compiler to do it
        for(size_t i=0; i!=N; ++i)
            array[i] *= x;
        */
        return *this;   
    }
  private:
    template<size_t I=0, typename Lambda>
    static enable_if_t<(I<N)> loop(Lambda &&lambda)            
    {
        lambda(I);
        loop<I+1>(lambda);
    }
    template<size_t I=0, typename Lambda>
    static enable_if_t<(I>=N)> loop(Lambda &&)         {}
};
like image 144
Walter Avatar answered Dec 20 '22 08:12

Walter


It is called template specialization and works as follows:

#include <cstdint>
#include <iostream>
    
struct Container1 {
    static constexpr size_t size = 5;
};
struct Container2 {
    static constexpr size_t size = 3;
};
    
    
template <typename Container>
struct Point {
    Container container;
    
    void bar() {
        this->bar_templated<Container::size>();
    }
    
private:
    template<std::size_t size>
    void bar_templated() {
        std::cout << "default\n";
    }
    template<>
    void bar_templated<3>() {
        std::cout << "specialized <3>\n";
    }
    template<>
    void bar_templated<5>() {
        std::cout << "specialized <5>\n";
    }
};
    
int main(){
    Point<Container1> p1;
    p1.bar();
    Point<Container2> p2;
    p2.bar();
}

output

specialized <5>
specialized <3>

due to bug 85282 in gcc making it impossible to compile an explicit specialization in non-namespace scope (thanks @songyuanyao), there are errors:

25:14: error: explicit specialization in non-namespace scope 'struct Point'

26:27: error: template-id 'bar_templated<3>' in declaration of primary template

...

30:10: error: 'void Point::bar_templated()' cannot be overloaded

But you can workaround this by moving the functions out of the class and still achieve specialization:

template<std::size_t size>
void bar_templated() {
    std::cout << "default\n";
}
template<>
void bar_templated<3>() {
    std::cout << "specialized 3\n";
}
template<>
void bar_templated<5>() {
    std::cout << "specialized 5\n";
}


template <typename Container>
struct Point {
    Container container;

    void bar() {
        bar_templated<Container::size>();
    }
};

This way the functions are public, which may not be what you want, though. Well, if you're writing inside a header file you could define them inside an anonymous namespace.


Also: if constexpr - but that's only C++17 and forward. It reducing the code size by alot and keeping it's logical nature makes it the best approach here, for sure.

void bar() {
    if constexpr (Container::size == 3) {
        std::cout << "specialized <3>\n";
    }
    else if constexpr (Container::size == 5) {
        std::cout << "specialized <5>\n";
    }
}
like image 28
Stack Danny Avatar answered Dec 20 '22 06:12

Stack Danny