Частичная специализация интегральной константы против типа

Можно ли специализировать шаблон на основе того, является ли аргумент шаблона либо типом, либо целочисленной константой? Вот пример, который не компилируется, но иллюстрирует намерение:

#include <cstdio>
#include <cstddef>

// tag struct
struct DynamicSize {};

template<class T, class Size>
class Container;

template<class T>
class Container<T, DynamicSize>
{
public:
    Container() {
        std::puts("dynamic size");
    }
};

template<class T, int Size>
class Container<T, Size>
{
public:
    Container() {
        std::puts("static size");
    }
};

int main(int argc, char* argv[]) {
    Container<char, 20> a;
    Container<char, DynamicSize> b;
}

Библиотека Eigen поддерживает матрицы фиксированного размера или размера, определяемого во время выполнения, и делает что-то похожее на это. Реализация заключается в том, что аргумент шаблона всегда является целочисленной константой, а динамический тег - просто константой, равной -1, но мне любопытно, есть ли другой способ.


person Praxeolitic    schedule 01.06.2016    source источник


Ответы (2)


Я думаю, что единственный способ сделать это — заставить шаблон Container принимать параметр типа для Size. Затем тип отвечает за обработку того, является ли он динамическим или статическим. Я думаю, что любые детали, относящиеся к типу хранилища, могут быть ограничены типами struct. Возможно, будет полезен следующий пример.

Пример кода

#include <iostream>

struct DynamicSize
{
    static const int size = -1;
};

template<int Size>
struct FixedSize
{
    static const int size = Size;
};

template<class T, class Size>
class Container
{
public:
    Container()
    {
        std::cout << Size::size << "\n";
    }
};

int main()
{
    Container<char, FixedSize<20>> a;
    Container<char, DynamicSize> b;

    return 0;
}

Пример вывода

20
-1

Живой пример

person James Adkison    schedule 02.06.2016
comment
Классические черты подхода. Кроме того, при таком подходе Size может по умолчанию использовать конкретную черту, если явно не указано иное, например: template<class T, class Size = DynamicSize> class Container {...}; ... Container<char> b; - person Remy Lebeau; 02.06.2016

Предложенное Джеймсом решение очень приятное!

Однако, если существует реальная потребность в частичной специализации, как это первоначально запрашивалось в вопросе (например, для реализации другого поведения в двух разных случаях), опираясь на ответ Джеймса Я бы предложил:

#include <iostream>

struct DynamicSize {};

template<int Size>
struct FixedSize {};

template<class T, class Size>
class Container;

template<class T, int size>
class Container<T, FixedSize<size>> {
public:
    Container() { std::cout << "FixedSize: " << size << "\n"; }
};

template<class T>
class Container<T, DynamicSize> {
public:
    Container() { std::cout << "dynamic size\n"; }
};

// you can also do that    
template<class T>
class Container<T, FixedSize<0>> {
public:
    Container() { std::cout << "Special case of FixedSize 0\n"; }
};

int main() {
    Container<char, FixedSize<20>> a;
    Container<char, DynamicSize> b;
    Container<char, FixedSize<0>> c;
}

Пример вывода

FixedSize: 20
dynamic size
Special case of FixedSize 0

Но зачем останавливаться здесь?

Если есть необходимость специализироваться на самом размере (т. е. создать различную реализацию для разных значений фиксированного размера путем выбора предопределенного диапазона), можно сделать следующее:

enum class SIZE_RANGE {SMALL, MEDIUM = 10, BIG = 100};
constexpr bool operator<(int size, SIZE_RANGE s) {
    return size < (int)s;
}

//------------------------------------------------------------------------------------
// FixedSize manages its own range selection here
template<int Size, 
         SIZE_RANGE SizeRange = (Size < SIZE_RANGE::MEDIUM? SIZE_RANGE::SMALL :
                                 Size < SIZE_RANGE::BIG?    SIZE_RANGE::MEDIUM :
                                                            SIZE_RANGE::BIG)>
struct FixedSize {};
//------------------------------------------------------------------------------------

Теперь мы можем специализироваться на размере Контейнера в соответствии с его диапазоном:

template<class T, class Size>
class Container;

template<class T>
class Container<T, FixedSize<0>> {
public:
    Container() { std::cout << "Special case of FixedSize 0\n"; }
};

template<class T, int size>
class Container<T, FixedSize<size, SIZE_RANGE::SMALL>> {
public:
    Container() { std::cout << "Small FixedSize: " << size << "\n"; }
};

template<class T, int size>
class Container<T, FixedSize<size, SIZE_RANGE::MEDIUM>> {
public:
    Container() { std::cout << "Medium FixedSize: " << size << "\n"; }
};

template<class T, int size>
class Container<T, FixedSize<size, SIZE_RANGE::BIG>> {
public:
    Container() { std::cout << "Big FixedSize: " << size << "\n"; }
};

template<class T>
class Container<T, DynamicSize> {
public:
    Container() { std::cout << "dynamic size\n"; }
};

Что может быть продемонстрировано следующими основными:

int main() {
    Container<char, DynamicSize> a;
    Container<char, FixedSize<0>> b;
    Container<char, FixedSize<5>> c;
    Container<char, FixedSize<42>> d;
    Container<char, FixedSize<100>> e;
}

Пример вывода

dynamic size
Special case of FixedSize 0
Small FixedSize: 5
Medium FixedSize: 42
Big FixedSize: 100

Живой пример

person Amir Kirsh    schedule 04.06.2016