Могу ли я заставить пользователя указать значение члена при использовании назначенных инициализаторов?

Я создал структуру параметров, предназначенную для использования через назначенный инициализатор:

struct FooOptions {
    bool enableReticulation;
};

void Foo(FooOptions&& opts);

Foo(FooOptions{.enableReticulation = true});

К сожалению, поскольку bool имеет конструктор по умолчанию, это также допустимо:

Foo(FooOptions{});

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

struct FooOptions {
    bool enableReticulation = []() -> bool { 
        assert(false, "No value provided for enableReticulation");
    }();
};

Но я бы предпочел сделать это с ошибкой времени компиляции. Есть ли способ сделать это? Я могу изменить bool на SomeWrapper<bool>, если это необходимо, пока я могу в основном инициализировать SomeWrapper<T>, как если бы это был T, но без инициализатора по умолчанию.


person Drew    schedule 02.11.2019    source источник
comment
Конечно, это именно то, как вы это делаете, используя тип оболочки с конструктором не по умолчанию. Как сказал бы капитан Жан-Люк Пикард: сделайте так.   -  person Sam Varshavchik    schedule 02.11.2019
comment
Проблема в том, что я хотел бы, чтобы SomeWrapper<T> можно было построить всеми способами, которыми можно построить T, кроме построения по умолчанию. Для простых типов, таких как bool, это легко, но если у меня есть SomeWrapper<std::vector<int>>, я хочу иметь возможность инициализировать std::initializer_list<int>, пару итераторов, распределитель и все другие способы инициализации std::vector.   -  person Drew    schedule 02.11.2019
comment
Вы можете использовать вариативный шаблон для второго аргумента и перенаправить все аргументы   -  person parktomatomi    schedule 02.11.2019
comment
Так? Либо делегируйте конструктор, и явно delete конструктор по умолчанию, либо используйте конструктор пересылки хотя бы с одним параметром.   -  person Sam Varshavchik    schedule 02.11.2019
comment
Я пробовал несколько вещей в этом направлении, но безуспешно :( Я либо получаю несколько перегрузок, разрешающих один и тот же тип, что недопустимо, либо я заканчиваю тем, что оболочка по-прежнему является конструктивной по умолчанию для пустого пакета параметров.   -  person Drew    schedule 02.11.2019


Ответы (2)


Способ обработки классов и типов, не относящихся к классам, благодаря SFINAE:

template<typename T, typename Enabler = void> class TWrapper;

template<typename T>
class TWrapper<T, std::enable_if_t<std::is_class<T>::value>> : public T {
public:
    TWrapper()=delete;

    using T::T;
};

template<typename T>
class TWrapper<T, std::enable_if_t<!std::is_class<T>::value>>
{
public:
    TWrapper()=delete;

    T value;
    TWrapper(T arg) : value(arg) {}
    operator T() const { return value; }
};

Демо

person Jarod42    schedule 02.11.2019

Вы уточнили, что речь идет о произвольных классах, а не о примитивных типах. Для произвольных классов с произвольными конструкторами: просто удалите конструктор, но явно delete конструктор по умолчанию:

template<typename T> class SomeWrapper : public T {

    SomeWrapper()=delete;

    using T::T;
};

Затем:

#include <vector>

foo F{ {1,2} }; // Works, initializes the vector with {1,2}

foo G{}; // Fails

Это может не работать так, как вы хотите для примитивных типов. Просто специализируется на SomeWrapper по мере необходимости. Существует не так много примитивных типов, с которыми можно иметь дело.

person Sam Varshavchik    schedule 02.11.2019
comment
Спасибо! К счастью, в моем случае меня интересуют только bool, int, float и double для примитивных типов, так что это прекрасно работает. Я даже не думал об использовании наследования. - person Drew; 02.11.2019