BCS
BCS

Reputation: 78633

C++ type that can only be used as constexpr variable

I have a library type that is only supposed to be used as a global variable and must be linker initialized (i.e. it must have the correct initial values before static initialization time). I have strong reason to think that I will get what I need if I do one of two things:

  1. make it a POD type, place the user provided members first and trust the user to pass the correct number of expressions to the Type var = { expr, expr }; syntax.
  2. make the implementation private, provide a constexpr constructor and depend on the user to declare all instances as constexpr.

Neither of these is nice in that it depends on the user not mucking things up.

Short of macro magic, is there any way to enforce that all instances of a type be constexpr?

Upvotes: 6

Views: 583

Answers (2)

dyp
dyp

Reputation: 39141

What you cannot prevent is users declaring const& to any instances created. However, you can prevent copying and moving instances. Now you'll only need to enforce that all instances created are created in a context where a constant expression is required.

Here's a weird way to enforce this: Let all instances be static constexpr members of a class (template).

The user then provides a way to get the constructor parameters of your "variable".

struct constructor_params
{
    int i;
    double d;
};

The instance provided by the user has to be usable in a constant expression in order to initialize the static constexpr member.

In order to create different instances, we need some kind of tag value to create different instantiations of a class template that contains the static constexpr member that'll serve as variable instance. I chose to combine the tag value and the way of supplying the constructor_params parameter by letting the user provide a factory function or type to create the parameter.

First, the variable type you want only to have constexpr instances of:

// forward declarations useful for friendship
template<class T>
struct make_variable_by_type;

template<constructor_params(*fptr)(void)>
struct make_variable_by_func;


struct the_variable
{
    the_variable(the_variable const&) = delete;
    the_variable(the_variable&&) = delete;

private:
    constexpr the_variable(constructor_params) {}

    template<class T>
    friend struct make_variable_by_type;

    template<constructor_params(*fptr)(void)>
    friend struct make_variable_by_func;
};

In order to let the user access both ways to create a variable with one name, there is an overloaded make_variable function:

template<constructor_params(*fptr)(void)>
struct make_variable_by_func
{
    static constexpr the_variable value{fptr()};
};
template<constructor_params(*fptr)(void)>
const the_variable make_variable_by_func<fptr>::value;

template<class T>
struct make_variable_by_type
{
    static constexpr the_variable value{T::make()};
};
template<class T>
const the_variable make_variable_by_type<T>::value;


template<class T>
constexpr the_variable const& make_variable()
{
    return make_variable_by_type<T>::value;
}

template<constructor_params(*fptr)(void)>
constexpr the_variable const& make_variable()
{
    return make_variable_by_func<fptr>::value;
}

Now, two usage examples. One with a constexpr function to create the constructor_params and one with a local type (the function scope is the reason why the creation from type is necessary).

constexpr constructor_params make_my_variable()
{
    return {42, 21.0};
}

constexpr auto& x = make_variable<make_my_variable>();

int main()
{
    struct make_my_other_variable
    {
        static constexpr constructor_params make()
        {
            return {1, 2};
        }
    };

    constexpr auto& x = make_variable<make_my_other_variable>();
}

Upvotes: 1

Jarod42
Jarod42

Reputation: 218098

Create a

template<Type1 value1, Type2 value2>
constexpr MyType make_MyTYpe(/* No args */)
{
    return MyType(value1, value2); // call the (private) constexpr constructor
}

And if your type provide only const method, user have to use const object.

const MyType myobject = make_MyType<4, 2>();

myobject is const from a constexpr.

Upvotes: 1

Related Questions