I have a class whose constructor takes some vectors and stores them.
struct X {
X(std::vector<int> const& ints, std::vector<float> const& floats, std::vector<std::string> const& strings)
: ints_{ints}
, floats_{floats}
, strings_{strings}
{};
std::vector<int> ints_;
std::vector<float> floats_;
std::vector<std::string> strings_;
};
I would like to make the member variables into references, because in production code the values passed to the constructor are lvalues, whose lifetimes are longer than the object of class X.
However, unit tests usually construct X
es with temporaries, like this:
X x{ {42}, {3.14f}, {"hello"} };
If the members of X
are to be references, such calls should be prevented. This can be done by writing a constructor that takes rvalue references and making it =delete
.
X(std::vector<int> && ints, std::vector<float> && floats, std::vector<std::string> && strings) = delete;
This would prevent the instantiation if all arguments are temporaries. Unfortunately, it lets through calls where at least one argument is an lvalue:
std::vector<std::string> no_strings;
X x{ {42}, {3.14f}, no_strings };
because lvalue references are willing to bind to rvalues, so the (const&,const&,const&)
constructor can be called.
Do I have to write every combination of lvalue/rvalue ref arguments (all seven of them) and mark all of them as deleted?
template<class T, class U>
using is_lvalue_reference_to_possibly_const = std::integral_constant<bool,
std::is_same<T, const U&>::value || std::is_same<T, U&>::value>;
template<class VI, class VF, class VS>
using check_value_cat_for_ctor = typename std::enable_if<
is_lvalue_reference_to_possibly_const<VI, std::vector<int>> &&
is_lvalue_reference_to_possibly_const<VF, std::vector<float>> &&
is_lvalue_reference_to_possibly_const<VS, std::vector<string>>>::type;
template<class VI, class VF, class VS, class = check_value_cat_for_ctor<VI, VF, VS>>
X(VI&&, VF&&, VS&&) {
// ...
}
How about a function with non-const lvalue ref arguments? Temporaries cannot bind to those so this should be what you are looking for.
Something like this:
#include <string>
#include <vector>
struct X {
X(std::vector<int>& ints,
std::vector<float>& floats,
std::vector<std::string>& strings)
: ints_{ints}
, floats_{floats}
, strings_{strings}
{};
std::vector<int>& ints_;
std::vector<float>& floats_;
std::vector<std::string>& strings_;
};
int main()
{
X x{ {42}, {3.14f}, {"hello"} };
}
The only thing you lose is a bit of const
correctness., which can be as thin as only the constructor. That is, if you want const
at all. Your question does not make that clear at all.
Because the templates in @T.C.'s answer make my eyes bleed, here's my version of those if you were more inclined towards that solution:
#include <string>
#include <type_traits>
#include <vector>
template<typename ... Ts>
constexpr bool are_not_temporaries = (std::is_lvalue_reference<Ts>::value&&...);
struct X
{
template<typename VI, typename VF, typename VS,
typename = std::enable_if_t<are_not_temporaries<VI, VF, VS>, void>>
X(VI&& ints, VF&& floats, VS&& strings)
: ints_{ints}
, floats_{floats}
, strings_{strings}
{};
std::vector<int>& ints_;
std::vector<float>& floats_;
std::vector<std::string>& strings_;
};
int main()
{
X x{ {42}, {3.14f}, {"hello"} };
}
This uses C++17 fold expressions to expand the parameter pack over the variadic template arguments. In case C++17 is unavailable, you can replace that with:
template<typename T1, typename T2, typename T3>
constexpr bool are_not_temporaries = std::is_lvalue_reference<T1>::value
&& std::is_lvalue_reference<T2>::value
&& std::is_lvalue_reference<T3>::value;
Which is admittedly a lot less nice.
What if you just defer to the library:
template <typename T>
using vector_ref = std::reference_wrapper<std::vector<T> const>;
struct X {
X(vector_ref<int> ints, vector_ref<float> floats, vector_ref<std::string> strings);
};
std::reference_wrapper
is already only constructible from lvalues, so we don't need to do all that work ourselves.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With