Я пытаюсь написать универсальный компонент, который обрабатывает данные, идентифицируемые в настоящее время перечислением. Данные могут быть разных типов, но один идентификатор относится только к одному типу.
Чего я пытаюсь добиться, так это специализировать шаблон где-нибудь в моей базе кода и вызывать функцию только по их идентификатору, например
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>();
Все известно во время компиляции и должно быть типобезопасным.
Честно говоря, я не понимаю, что вы хотите сделать...
Привет. Есть ли особая причина, по которой шаблон должен быть шаблоном функции-члена, а не шаблоном класса?
А как насчет диспетчеризации тегов, понятия программирования шаблонов?





Вы имеете в виду, разделить декларацию на две части?
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;
}
Можете ли вы дать мне сценарий, как это использовать? я не молчу понимаю...
Что ты не понимаешь? Я имею в виду, что синтаксис, который хотел OP, работает с обоими моими примерами.
Чего я не понимаю, так это цель всего этого. Поэтому, если вы можете привести пример того, как ваш код полезен от всего этого, я буду очень признателен!
Я тоже не понимаю цели. Я действительно понятия не имею, что OP хочет сделать с Test::get<ID2>(). Все, что я знаю, это то, что ОП хочет, чтобы этот синтаксис работал с перечислением и специализацией.
Решение С++ 17 (самое нижнее) работает лучше всего, но только когда все находится в одном блоке компиляции, потому что автоматический вывод не известен в других блоках (я не знаком, возможно ли объявить его в заголовочный файл) .
Шаблоны @MarcLühr обычно реализуются в файлах заголовков. Исключением из правила является специализация, но действительно вывод типа возвращаемого значения требует помещения его в заголовок. Только не забудьте отметить его в строке.
Я полагаю, ты хотел этого?
#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.
Нет необходимости в перечислении
Мы не в том месте, чтобы принимать это решение за них.
Когда кто-то что-то спрашивает, может быть, есть неправильный подход, из-за непонимания чего-то. Это одна из причин для вопроса. Спрашивающий оценит, так это или нет. Если вы делаете это вместо него, это означает, что вы уже знаете, что у него на уме, что обычно трудно сделать.
Использование только typedefs было моей первой попыткой, но когда у меня есть два идентификатора, например, оба int, это становится неоднозначным.
Я нашел способ использовать части других ответов и показать реализацию здесь:
центральный заголовочный файл для определения всех идентификаторов и типов. 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;
}
Это в значительной степени взято из ответов Джорджа Куртиса и Гийома Расико и добавлено еще немного от себя.
Что вы имеете в виду под
type in get line?