Специализация шаблона с использованием переменного аргумента

Я пытаюсь обернуть вариант стиля c в библиотеку. Он использует тип enum, который я имею в виду для заполнения с помощью шаблонов.

Упрощенная версия выглядит примерно так:

enum Type
{ 
    Type_Int,
    Type_Double,
    Type_String
};

template<typename T>
Type typeId();

template<> Type typeId<int>()   { return Type_Int;  }
template<> Type typeId<double>(){ return Type_Double;  }
template<> Type typeId<const char*>()   { return Type_String;  }

template<> Type typeId<char[10]>()   { return Type_String;  }

// not allowed
// template<int N> Type typeId<char[N]>()   { return Type_String;  }

https://godbolt.org/z/3GsKv6PEn

Я прекрасно распознаю char[10], но хочу сделать общий случай char[N], что недопустимо. Так как же мне распознать все типы char[N]?

пс. Он встроен, поэтому я предпочитаю не использовать std::string там, где это возможно.

Чтобы иметь частичную специализацию, вам нужен класс. Итак, создайте логику шаблона в классе, а затем используйте этот класс в этой функции.

Marek R 08.04.2024 15:29

Возможно, вы могли бы показать немного больше API «C», которым вам приходится довольствоваться. На самом деле, возможно, было бы проще использовать std::variant на стороне C++ и написать функцию преобразования.

Pepijn Kramer 08.04.2024 15:35

Пожалуйста, также включите определение struct/union, которое используется на стороне C.

Ted Lyngmo 08.04.2024 15:40

С помощью c++20 вы можете объединить концепцию и std::is_array как это сделано здесь.

user12002570 08.04.2024 15:46

На самом деле это не вариант библиотеки, но они используют перечисление и стирание типов. Это вот этот для тех, кому интересно, но это будет интересно прочитать всем, кто отвечает на простой ТАК вопрос :)

David van rijn 08.04.2024 15:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
150
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Как насчет:

template<int N> Type typeId(char(& a)[N])   { return Type_String;  }

Это не совсем тот же синтаксис, но он будет работать так же, как и предыдущий, поскольку вы можете указать тип с помощью параметра шаблона.

См. пример

И кстати, для этого маппинга вам вообще не нужен шаблон. Решение с базовой перегрузкой функций будет работать:

#include <iostream>

enum Type{ 
    Type_Int,
    Type_Double,
    Type_String,
};

constexpr Type typeId(int)   { return Type_Int;  }
constexpr Type typeId(double){ return Type_Double;  }
constexpr Type typeId(const char*)   { return Type_String;  }

int main(int argc, char** argv)
{
    int e;
    std::cout << typeId(e) << std::endl;
    double d;
    std::cout << typeId(d) << std::endl;
    const char * c;
    std::cout << typeId(c) << std::endl;
    char b[10];
    std::cout << typeId(b) << std::endl;
    char a[11];
    std::cout << typeId(a) << std::endl;

}

Функция может быть constexpr, поэтому вы также можете извлечь Type во время компиляции.

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

#include <iostream>

enum Type{ 
    Type_Int,
    Type_Double,
    Type_String,
};

template <typename T> constexpr Type Map;
template <> constexpr Type Map<int> = Type_Int;
template <> constexpr Type Map<double> = Type_Double;
template <> constexpr Type Map<const char*> = Type_String;
template <size_t N> constexpr Type Map<char[N]> = Type_String;


int main(int argc, char** argv)
{
    std::cout << Map<int> << std::endl;
    std::cout << Map<double> << std::endl;
    std::cout << Map<const char*> << std::endl;
    std::cout << Map<char[10]> << std::endl;
    std::cout << Map<char[11]> << std::endl;
}

Полный пример с инверсией карты (Type к фактическому типу C++) здесь

Кроме того, для этого вам не нужен C++20 (в любом случае он может быть недоступен в вашем встроенном наборе инструментов разработки).

В вопросе typeId функция не имеет аргументов. Фактически, с этим решением вам вообще не нужны шаблоны: godbolt.org/z/P6Wr5Y38z разрешение перегрузки возьмет на себя.

Marek R 08.04.2024 15:56

Вы можете обернуть тип std::type_identity, чтобы избежать фиктивной переменной: constexpr Type typeId(std::type_identity<int>) { return Type_Int; }

Jarod42 08.04.2024 15:56

Есть так много способов сделать одно и то же. Я перечислил 3 из них. Последний, пожалуй, самый чистый.

xryl669 08.04.2024 16:20

@Jarod42: Да, правда. Дело в том, что вам не нужна специализация шаблонов, чтобы вывести тип C-распавшегося, достаточно только перегрузки функций.

xryl669 08.04.2024 16:21
Ответ принят как подходящий

Вы не можете частично специализировать функцию. Но вы можете частично специализировать класс или структуру.

Итак, чтобы решить эту проблему, выполните логику шаблона в классе, а затем используйте ее в функции:

enum Type {
    Type_Int,
    Type_Double,
    Type_String,
};

namespace detail {
template <typename T>
class TypeId;

template <>
class TypeId<int> {
public:
    static constexpr Type value = Type_Int;
};

template <>
class TypeId<double> {
public:
    static constexpr Type value = Type_Double;
};

template <>
class TypeId<const char*> {
public:
    static constexpr Type value = Type_String;
};

template <int N>
class TypeId<const char[N]> {
public:
    static constexpr Type value = Type_String;
};
}

template <typename T>
constexpr Type typeId()
{
    return detail::TypeId<T>::value;
}

static_assert(typeId<int>() == Type_Int);
static_assert(typeId<double>() == Type_Double);
static_assert(typeId<const char*>() == Type_String);
static_assert(typeId<const char[2]>() == Type_String);
static_assert(typeId<const char[10]>() == Type_String);

https://godbolt.org/z/4zfb47Mvx

Чтобы сделать это более устойчивым к вариациям типов cv-квалификаторов, вы можете использовать std::remove_cvref_t : https://godbolt.org/z/esGf4Wc8h

С c++20 вы можете использовать концепцию и написать черту/проверку, например std::is_array, чтобы проверить, является ли тип массивом символов, как показано ниже.

template<class T>
struct is_char_array : std::false_type {};
 
template<>
struct is_char_array<char[]> : std::true_type {};
 
template<std::size_t N>
struct is_char_array<char[N]> : std::true_type {};
template< class T >
inline constexpr bool is_char_array_v = is_char_array<T>::value;

template<typename T> concept checkArray = is_char_array_v<T>;

template<checkArray T> Type typeId()
  { 
    std::cout << "char N version\n";
    return Type_String;  
  }

Рабочая демо

Как мне распознать все типы char[N]?

Специализация классов должна быть предпочтительным способом, который работает всегда.

Однако в вашем случае вы можете использовать if constexpr следующим образом (требуется c++17 или более поздняя версия), чтобы создать одну функцию typeId() для всех Type случаев:

template<typename T>
inline constexpr Type typeId() /* noexcept */
{
    if constexpr (std::is_same_v<T, int>)  return Type_Int;
    else if constexpr (std::is_same_v<T, double>) return Type_Double;
    else if constexpr (std::is_same_v<T, const char*> 
        || (std::is_array_v<T> 
               && std::is_same_v<std::remove_const_t<std::remove_extent_t<T>>, char>)
        )
        return Type_String;
}

Посмотрите живую демонстрацию


Если существуют многомерные массивы char, вы можете заменить std::remove_extent_t на std::remove_all_extents в приведенном выше примере.

Другие вопросы по теме