Найти тип для шаблона по какому-то идентификатору или int

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

Чего я пытаюсь добиться, так это специализировать шаблон где-нибудь в моей базе кода и вызывать функцию только по их идентификатору, например

Test::get<ID2>()

Что работает в приведенном ниже коде, включая type. Есть ли простой/умный способ избежать написания типа в строке get?

Я попытался использовать typedef для идентификации, купив, конечно, то, что он напрямую преобразуется в примитивный тип, а две специализации шаблона неоднозначны. Если нет хорошего способа C++, я все еще могу использовать генерацию кода для решения этой проблемы, но я надеюсь на лучшее решение.

#include <iostream>

typedef int ID1_t;
typedef double ID2_t;

enum  {
    ID1,
    ID2,
};

class Test
{
public:
    template<int I, typename T> static T get();
};

template<> int Test::get<ID1>()
{
  return 43;   
}

template<> double Test::get<ID2>()
{
  return 0.12;   
}

int main()
{
  std::cout << Test::get<ID2, ID2_t>() << std::endl;
  std::cout << Test::get<ID1, ID1_t>() << std::endl;
}

РЕДАКТИРОВАТЬ (немного больше фона): Я пишу центральный компонент для использования для получения данных (например, int) и установки данных с помощью механизма уведомления, чтобы информировать все необходимые места. Его централизованное определение, но децентрализованная реализация, что интерфейс одинаков для всех применений, но реализация может быть в собственном исходном файле. Это значительно снижает зависимость между компонентами, но также обеспечивает надежный обмен типизированными данными.

Например:

Допустим, у нас есть данные, записанные во FLASH-память во время производства, данные конфигурации хранятся в EEPROM или FRAM, а также некоторые энергозависимые данные в переменных RAM, которые будут забыты при включении питания.

Возьмем значение громкости динамика, которое можно как-то изменить. Я просто хочу ввести значение в систему:

auto volume = 55; //(constant here but is in real decoded from SPI)
CentralDataStorage::set<Volume>(volume);

Я не хочу делать зависимость от физического хранилища здесь. Чем мне нужен компонент, который отвечает за сохранение значения, а также за возврат значения по запросу. Таким образом, он реализует метод установки и получения, но только для идентификатора Объем.

Другой компонент, который фактически устанавливает громкость в усилителе, получает уведомление и использует метод get из центрального интерфейса, который реализован где-то, чего этот компонент не знает.

auto volume = CentralDataStorage::get<Volume>();

Все известно во время компиляции и должно быть типобезопасным.

Что вы имеете в виду под type in get line?

SergeyA 08.05.2019 21:17

Честно говоря, я не понимаю, что вы хотите сделать...

Tony 08.05.2019 21:25

Привет. Есть ли особая причина, по которой шаблон должен быть шаблоном функции-члена, а не шаблоном класса?

Spencer 08.05.2019 21:27

А как насчет диспетчеризации тегов, понятия программирования шаблонов?

Sandburg 08.05.2019 21:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
77
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы имеете в виду, разделить декларацию на две части?

template<int i> struct id_type;
template<int i> using id_type_t = typename id_type<i>::type;
template<> struct id_type<ID1> { using type = int; }

template<int i> id_type_t<i> get();
Ответ принят как подходящий

Сначала я объясню свой предпочтительный способ делать такие вещи. Обычно я стараюсь избегать enum, так как почти каждый раз, когда вам нужны дополнительные данные, а с enum вы застреваете в записи этих данных в другом месте. Например, что в другом месте может быть в специализации шаблона.

Я бы предпочел использовать типы:

struct ID1 {
    static constexpr auto default_value = 42; 
};

struct ID2 {
    static constexpr auto default_value = 0.12; 
};

Затем отбросьте специализацию и используйте данные, представленные в типах:

struct Test {
    template<typename T>
    static auto get() {
        // or maybe call a static member function
        return T::default_value;
    }
};

int main()
{
  std::cout << Test::get<ID2>() << std::endl;
  std::cout << Test::get<ID1>() << std::endl;
}

Но есть способ заставить ваш дизайн работать и избежать написания этого типа с помощью вывода возвращаемого типа:

#include <iostream>

enum {
    ID1,
    ID2,
};

struct Test {
    // to be deduced -------v
    template<auto I> static auto get();
    //       ^--- (optional change)
    //            Have the enum type deduce as well
};

template<> auto Test::get<ID1>()
{
  return 43;   
}

template<> auto Test::get<ID2>()
{
  return 0.12;   
}

int main()
{
  std::cout << Test::get<ID2>() << std::endl;
  std::cout << Test::get<ID1>() << std::endl;
}

Посмотрите, как он компилируется в проводнике компилятора.

Вы также можете вернуться к int, поскольку параметр шаблона перечисления остается совместимым с C++ 14:

template<int I> static auto get();
//       ^------ reverted it back to int

Но используя С++ 17 и параметры автоматического шаблона, вы можете иметь несколько перечислений без конфликта значений:

enum struct A {
    Value1, Value2
};

enum struct B {
    Value1
};

// Different with C++17 auto template parameter
// not possible with C++14
template<> auto Test::get<A::Value1>()
{
  return 43;   
}

template<> auto Test::get<B::Value1>()
{
  return 0.12;   
}

Можете ли вы дать мне сценарий, как это использовать? я не молчу понимаю...

Tony 08.05.2019 22:36

Что ты не понимаешь? Я имею в виду, что синтаксис, который хотел OP, работает с обоими моими примерами.

Guillaume Racicot 08.05.2019 22:58

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

Tony 09.05.2019 00:41

Я тоже не понимаю цели. Я действительно понятия не имею, что OP хочет сделать с Test::get<ID2>(). Все, что я знаю, это то, что ОП хочет, чтобы этот синтаксис работал с перечислением и специализацией.

Guillaume Racicot 09.05.2019 04:29

Решение С++ 17 (самое нижнее) работает лучше всего, но только когда все находится в одном блоке компиляции, потому что автоматический вывод не известен в других блоках (я не знаком, возможно ли объявить его в заголовочный файл) .

Marc Lühr 09.05.2019 11:04

Шаблоны @MarcLühr обычно реализуются в файлах заголовков. Исключением из правила является специализация, но действительно вывод типа возвращаемого значения требует помещения его в заголовок. Только не забудьте отметить его в строке.

Guillaume Racicot 09.05.2019 15:12

Я полагаю, ты хотел этого?

#include <iostream>

typedef int ID1;
typedef double ID2;

class Test
{
public:
    template<typename T> static T get();
};

template<> int Test::get<ID1>()
{
  return 43;   
}

template<> double Test::get<ID2>()
{
  return 0.12;   
}

int main()
{
  std::cout << Test::get<ID2>() << std::endl;
  std::cout << Test::get<ID1>() << std::endl;
}

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

ОП хочет определить ID1 и ID2 как токены enum.

R Sahu 08.05.2019 21:40

Нет необходимости в перечислении

George Kourtis 08.05.2019 21:52

Мы не в том месте, чтобы принимать это решение за них.

R Sahu 08.05.2019 21:54

Когда кто-то что-то спрашивает, может быть, есть неправильный подход, из-за непонимания чего-то. Это одна из причин для вопроса. Спрашивающий оценит, так это или нет. Если вы делаете это вместо него, это означает, что вы уже знаете, что у него на уме, что обычно трудно сделать.

George Kourtis 08.05.2019 22:32

Использование только typedefs было моей первой попыткой, но когда у меня есть два идентификатора, например, оба int, это становится неоднозначным.

Marc Lühr 09.05.2019 08:37

Я нашел способ использовать части других ответов и показать реализацию здесь:

центральный заголовочный файл для определения всех идентификаторов и типов. decltype помогает найти возвращаемый тип. Использование авто здесь приводит меня к проблеме, когда я объявляю здесь метод для определенного типа, он не будет соответствовать объявлению шаблона.

//Test.hpp
struct ID1 {
    int value; 
};

struct ID2 {
    double value; 
};

struct Test {
    template<typename T> static decltype(T::value) get();
};

Затем у меня есть определение функции get (которая также может быть в отдельных единицах компиляции)

#include "Test.hpp"
template<> int Test::get<ID1>()
{
    return 43;
}

template<> double Test::get<ID2>()
{
    return 0.12;
}

и основной, который их использует

#include "Test.hpp"
int main()
{
  std::cout << Test::get<ID2>() << std::endl;
  std::cout << Test::get<ID1>() << std::endl;
}

Это в значительной степени взято из ответов Джорджа Куртиса и Гийома Расико и добавлено еще немного от себя.

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