Вопрос или проблема
C++ вариативные шаблоны с значениями вместо типов
В C++ есть ли эквивалент вариативного шаблона с значениями вместо typename или class?
Внутри метода шаблонного класса идея состоит в том, чтобы использовать значения, которые указаны в объявлении экземпляра, без обращения к конструктору.
Это могло бы выглядеть как-то так (корректный синтаксис еще не найден):
template <class T, "какой-то вариативный синтаксис значений...">
class Sample
{
public:
T val() { return T("какой-то вариативный синтаксис значений..."); }
};
Sample <std::string> a; // a.val() возвращает ""
Sample <std::string, "foo"> b; // b.val() возвращает "foo"
Sample <std::string, 4, 'a'> c; // c.val() возвращает "aaaa"
В настоящее время эквивалент достигается с помощью макросов препроцессора, от которых я хотел бы избавиться и заменить их шаблонами.
Любая другая идея приветствуется, если:
- не используется макросы,
- используется до C++17, но не более новые,
- нет необходимости передавать значения в конструктор,
- значения не хранятся внутри экземпляра объекта,
- конструктор объекта, возможно, еще не был вызван (статический объект внутри еще не инициализированного модуля),
- разрешено любое количество значений, включая ноль.
Вы можете использовать тип-заместитель auto
, как показано ниже. Обратите внимание, что строковые литералы не разрешены в качестве шаблонных аргументов в текущем C++. Поэтому второй пример Sample <std::string, "foo"> b;
никогда не сработает, потому что здесь "foo"
не может быть передан. Вы можете использовать литералы, определяемые пользователем, в качестве обходного пути.
template <class T, auto...>
class Sample
{
public:
T val() { return T("какой-то вариативный синтаксис значений..."); }
};
Этот синтаксис нецелесообразен, поскольку большинство типов не могут быть шаблонными параметрами. Среди прочего, вы не можете передавать строковые литералы (есть обходные пути, но они не работают в вашем случае).
Единственное работоспособное решение — передать функциональный объект, например, лямбда-функцию:
template <class T, auto F>
class Sample
{
public:
T val() { return F(); }
};
Sample<std::string, []{return std::string{};}> a; // a.val() возвращает ""
Sample<std::string, []{return std::string("foo");}> b; // b.val() возвращает "foo"
Sample<std::string, []{return std::string(4, 'a');}> c; // c.val() возвращает "aaaa"
Но это требует C++20. До C++20 вам придется заменить лямбды функциями (или классами-функторми), так что синтаксис не будет выглядеть так хорошо.
Ответ или решение
В C++ нет предусмотренной возможности передавать значения в качестве параметров шаблона так, как это делается с типами. Однако, с использованием функции как параметра шаблона, вы можете достичь желаемого результата, хотя и с некоторыми ограничениями. Обратите внимание, что в C++17 и более ранних версиях не допускается передавать строковые литералы, поскольку они не являются допустимыми аргументами шаблона. Рассмотрим некоторые варианты решения вашей задачи.
Решение с использованием функций
Можно обернуть значения в функции или функторы, которые затем можно передать в шаблон:
#include <string>
#include <functional>
template <class T, auto Func>
class Sample {
public:
T val() { return Func(); }
};
// Примеры использования:
Sample<std::string, [] { return std::string{}; }> a; // a.val() возвращает ""
Sample<std::string, [] { return std::string("foo"); }> b; // b.val() возвращает "foo"
Sample<std::string, [] { return std::string(4, 'a'); }> c; // c.val() возвращает "aaaa"
Альтернативный подход с использованием структур
Если вы работаете с C++17 и не можете использовать функции, вы также можете создать структуру для хранения значений:
#include <string>
template <class T, auto... Values>
struct ValueHolder {
static T getValue(size_t index) {
if constexpr (sizeof...(Values) == 0) {
return T(); // Возврат пустого значения, если значений нет
} else {
// Пример, как можно извлечь конкретное значение, учитывая структуру
return T{Values...}; // Здесь можно адаптировать логику
}
}
};
template <class T, auto... Values>
class Sample {
public:
T val() { return ValueHolder<T, Values...>::getValue(0); }
};
// Примеры использования:
Sample<std::string, 'f', 'o', 'o'> b; // b.val() возвращает "foo"
Sample<std::string> a; // a.val() возвращает ""
Вывод
С помощью функции или структурных оберток вокруг значений можно создавать шаблоны, которые будут работать как вы ожидаете. Обратите внимание на ограничения по типам значений и на то, что в C++17 строковые литералы не могут быть переданы как аргумент шаблона. Использование функторов или структуры является наиболее универсальным способом достижения вашей цели в рамках указанных ограничений.