1

I have created an options struct, intended to be used via designated initializer:

struct FooOptions {
    bool enableReticulation;
};

void Foo(FooOptions&& opts);

Foo(FooOptions{.enableReticulation = true});

Unfortunately, because bool has a default constructor, it's also valid to do this:

Foo(FooOptions{});

but I don't want this; I want users of Foo to explicitly decide to enable reticulation or not. I can achieve this with a runtime error this way:

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

But I would prefer to do this with a compile time error. Is there any way to do that? I am OK with changing bool to SomeWrapper<bool> if necessary, as long I can mostly initialize SomeWrapper<T> as if it were T, but without a default initializer.

Drew
  • 12,578
  • 11
  • 58
  • 98
  • Sure, that's exactly how you to do this, by using a wrapper type with a non-default constructor. As Capt. Jean-Luc Picard would say: make it so. – Sam Varshavchik Nov 02 '19 at 00:35
  • The problem is that I would like the `SomeWrapper` to be constructible in all the ways `T` can be constructed, _except_ default constructed. For simple types like `bool` this is easy, but if I have `SomeWrapper>`, I want to be able to initialize with `std::initializer_list`, a pair of iterators, an allocator, and all the other ways a `std::vector` can be initialized. – Drew Nov 02 '19 at 00:39
  • You can use a variadic template for the second argument on and perfect-forward all the arguments – parktomatomi Nov 02 '19 at 00:41
  • So? Either a delegate the constructor, and explicitly `delete` the default constructor, or use a forwarding constructor with at least one parameter. – Sam Varshavchik Nov 02 '19 at 00:42
  • I have tried a few things along those lines with no success :( I either get multiple overloads resolving to the same type, which is not valid, or I end up with the wrapper still being default constructible, for empty parameter pack. – Drew Nov 02 '19 at 00:43

2 Answers2

2

You clarified that this is about arbitrary classes, and not primitive types. For arbitrary classes, with arbitrary constructors: just delete the constructor, but explicitly delete the default constructor:

template<typename T> class SomeWrapper : public T {

    SomeWrapper()=delete;

    using T::T;
};

Then:

#include <vector>

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

foo G{}; // Fails

This may not work like you want for primitive types. Just specialize SomeWrapper as needed. There aren't that many primitive types to deal with.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Thanks! Fortunately in my case I only care about `bool`, `int`, `float`, and `double` for primitive types, so this works great. I didn't even think about using inheritance. – Drew Nov 02 '19 at 01:13
1

Way to handle classes and non-classes types, thank to 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; }
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302