C++ вариативные шаблоны с значениями вместо типов

Вопрос или проблема

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 строковые литералы не могут быть переданы как аргумент шаблона. Использование функторов или структуры является наиболее универсальным способом достижения вашей цели в рамках указанных ограничений.

Оцените материал
Добавить комментарий

Капча загружается...