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>();
}