Избегайте написания конструктора каждого класса стилей объединения в C++ 17

Я застрял на 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 07.04.2021 19:18

Заводская функция

Woodford 07.04.2021 19:19

@GaryNLOL не могли бы вы уточнить? Я думаю, что у меня все еще есть проблема «Нужно найти тип переменной-члена из шаблона».

hythis 07.04.2021 19:25

FWIW, набирать текст через объединение недопустимо в C++. Я не видел, чтобы компилятор не выполнял то, что ожидает большинство людей, но вы находитесь в стране UB. в зависимости от того, что вы на самом деле делаете / хотите, std::variant является типобезопасным типом объединения, который вы можете использовать.

NathanOliver 07.04.2021 19:34

@NathanOliver Я хочу обрезать типы, потому что я реализую через них безопасные битовые поля, я не пытаюсь хранить взаимоисключающие типы

hythis 07.04.2021 19:40

@Eljay Меня смущает этот вопрос. Как я уже сказал в OP, существует более одного "примера". В моем случае больше 20. Я не хочу писать одно и то же снова и снова, и я В самом деле не хочу делать это в двух файлах.

hythis 07.04.2021 20:12
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не совсем то, что вы просили ... но вы можете использовать тот факт, что вы можете использовать тип 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 07.04.2021 21:16

@hythis - есть точка класса, в которой вы не знаете полностью («неполный тип»), как создается параметр шаблона (например: есть ли член value или тип этого члена). Итак, decltype(D::value) работает внутри тело метода, но вы не можете использовать его для создания сигнатуры самого метода.

max66 07.04.2021 21:39

Я считаю, что есть немного неопределенное поведение, поскольку объекты создаются от самого базового до самого производного порядка, поэтому доступ к value в конструкторе Base означает доступ к переменной-члену еще не созданного объекта. Может быть, на практике проблем не возникнет, но есть о чем знать.

Eljay 07.04.2021 22:18

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