Я застрял на C++ 17 для проекта, поэтому у меня нет доступа к назначенным инициализаторам. У меня есть несколько типов объединения, которые я хочу избежать такой инициализации (потому что это раздражает):
MyUnionType x;
x.value = value_set_for_all_union_members;
Я хочу вместо этого иметь это
MyUnionType x(value_set_for_all_union_members);
Но я также хочу избегать написания реализации для каждого создаваемого союза. Я знаю, что все мои типы объединений будут иметь следующую структуру, каждое объединение предназначено для представления бит-битового поля, поэтому я действительно хочу обрезать тип здесь, я знаю, что это "UB" в соответствии с C++, но это входит в комитет C++, в C это не неопределенное поведение, и, следовательно, все компиляторы, которые мне небезразличны, будут делать здесь то, что я хочу.
union Example{
integer_type value;
custom_safe_bitfield_abstraction<...> a;
custom_safe_bitfield_abstraction<...> b;
...
};
Я подумал, хорошо, я просто унаследую конструктор и воспользуюсь CRTP для извлечения соответствующего integer_type. Конечно, я не могу наследовать объединение напрямую, поэтому вместо этого я выбрал следующую стратегию:
struct Example : Base<Example>{
union{
integer_type value;
custom_safe_bitfield_abstraction<...> a;
custom_safe_bitfield_abstraction<...> b;
...
};
};
используя анонимное объединение, я должен иметь возможность использовать его точно так же, как и раньше (example.value должно быть значением внутри объединения).
Затем в реализации делаю следующее:
template<class Derived_T>
struct Base{
using value_type = decltype(Derived_T::value);
explicit Base(value_type v){
static_cast<Derived_T*>(this)->value = v;
}
}
Однако это не работает:
error: Incomplete type 'Example' used in nested name specifier
> using value_type = decltype(Derived_T::value);
Очевидно, нам не разрешено ссылаться на член до того, как он был объявлен. Хорошо ... но должен быть какой-то способ извлечь данные типа, в конце концов, меня не волнует выравнивание памяти или что-то еще.
Единственное, что я могу придумать, - это включить тип в параметр шаблона CRTP (например, Base<Derived_T, value_type>), но я не хочу этого делать. Я полагаю, что есть какой-то метод для написания функции или определения внутреннего типа для каждого производного класса, я тоже не хочу этого делать (и в любом случае это как бы поражает цель того, что я делаю).
Есть ли способ избежать написания конструктора для каждого класса и без ущерба для других целей минимизации дублирования кода, которые у меня есть?
Заводская функция
@GaryNLOL не могли бы вы уточнить? Я думаю, что у меня все еще есть проблема «Нужно найти тип переменной-члена из шаблона».
FWIW, набирать текст через объединение недопустимо в C++. Я не видел, чтобы компилятор не выполнял то, что ожидает большинство людей, но вы находитесь в стране UB. в зависимости от того, что вы на самом деле делаете / хотите, std::variant является типобезопасным типом объединения, который вы можете использовать.
@NathanOliver Я хочу обрезать типы, потому что я реализую через них безопасные битовые поля, я не пытаюсь хранить взаимоисключающие типы
@Eljay Меня смущает этот вопрос. Как я уже сказал в OP, существует более одного "примера". В моем случае больше 20. Я не хочу писать одно и то же снова и снова, и я В самом деле не хочу делать это в двух файлах.





Не совсем то, что вы просили ... но вы можете использовать тот факт, что вы можете использовать тип D::value внутри функции-члена ... поэтому использование SFINAE над конструктором шаблонов ...
Я имею в виду, вы можете написать что-нибудь как
template <typename D>
struct Base
{
template <typename T>
static constexpr bool is_value_type ()
{ return std::is_same_v<decltype(D::value), T>; }
template <typename T, bool B = is_value_type<T>(),
std::enable_if_t<B, int> = 0>
explicit Base (T v)
{ static_cast<D*>(this)->value = v; }
};
где конструктор шаблона включен, только если выведенный тип аргумента относится к тому же типу B::value.
Не забудьте также добавить using
using Base<Example>::Base;
внутри Example.
Ниже приведен полный пример компиляции.
#include <type_traits>
template <typename D>
struct Base
{
template <typename T>
static constexpr bool is_value_type ()
{ return std::is_same_v<decltype(D::value), T>; }
template <typename T, bool B = is_value_type<T>(),
std::enable_if_t<B, int> = 0>
explicit Base (T v)
{ static_cast<D*>(this)->value = v; }
};
struct Example : Base<Example>
{
using Base<Example>::Base;
union
{
long value;
long a;
long b;
};
};
int main ()
{
//Example e0{0}; // compilation error
Example e1{1l}; // compile
//Example e2{2ll}; // compilation error
}
Я думаю, что это именно то, что я ищу, по крайней мере, для моего варианта использования, также обратите внимание, что использование Base::Base; также работает вместо Base<T>::Base;. Что меня смущает, так это Зачем, это работает. Почему это удается, где также используется decltype дочернего значения, когда прямая попытка получить этот тип терпит неудачу? Это просто потому, что определение самого класса больше не зависит от конкретного типа или что-то в этом роде?
@hythis - есть точка класса, в которой вы не знаете полностью («неполный тип»), как создается параметр шаблона (например: есть ли член value или тип этого члена). Итак, decltype(D::value) работает внутри тело метода, но вы не можете использовать его для создания сигнатуры самого метода.
Я считаю, что есть немного неопределенное поведение, поскольку объекты создаются от самого базового до самого производного порядка, поэтому доступ к value в конструкторе Base означает доступ к переменной-члену еще не созданного объекта. Может быть, на практике проблем не возникнет, но есть о чем знать.
Я думаю, вы могли бы использовать функцию шаблона вместо наследования.