I do have a generic container like so:
template<typename T>
struct GenericContainer
{
T& at(size_t index)
{
return data.at(index);
}
void append(const T& value)
{
data.push_back(value);
}
std::vector<T> data;
};
As you all know we can't get a non const reference to bool through std::vector.at function.
So this does not compile : bool& b = std::vector<bool>(true).front()
For the same reason i can't call the function at on my GenericContainer.
I would like to enable the function at on my GenericContainer only if i can get a non const reference to T.
I tried to implement a type traits like that but it does not work, it returns true_type for bool and int, i am looking for false_type for bool and true_type for int.
It is inspired from a is_copy_assignable type traits.
template <typename T>
struct is_ref_extractable_from_vector
{
private:
template <class U, class ENABLED = decltype(std::declval<U&>() = std::declval<std::vector<U>>().front())>
static std::true_type try_assignment(U&&);
static std::false_type try_assignment(...);
public:
using type = decltype(try_assignment(std::declval<T>()));
};
template<typename T>
using is_ref_assignable_from_vector_t = typename is_ref_extractable_from_vector<T>::type;
template <typename T>
inline constexpr bool is_ref_assignable_from_vector_v = is_ref_assignable_from_vector_t<T>::value;
static_assert(!is_ref_assignable_from_vector_v<bool>);
static_assert(is_ref_assignable_from_vector_v<int>);
i have no clue of why the assignment in the enable_if clause always compile.
i was hopping SFINAE to discard try_assignment(U&&) overload with U = bool.
thanks for your help.
ps : note that i'm a bit novice in meta programming so i will not be suprised if there is an obvious reason for that result :)
CodePudding user response:
It is only std::vector<bool>
that has the broken specification, so the simplest way to write your trait is
template <typename T>
struct is_ref_extractable_from_vector : std::true_type {};
template <>
struct is_ref_extractable_from_vector<bool> : std::false_type {};
Alternatively, you could specialise GenericContainer
for bool
such that it doesn't use std::vector<bool>
class vector_bool {
// all the members of std::vector
private:
bool * data;
std::size_t size;
std::size_t capacity;
};
template<>
struct GenericContainer<bool> {
bool& at(size_t index)
{
return data.at(index);
}
void append(const bool& value)
{
data.push_back(value);
}
vector_bool data;
}
CodePudding user response:
std::vector<bool>
does not return a bool&
, but nevertheless this is completely fine:
std::vector<bool> x{0,0,0};
x.at(0) = false;
The reaons is that x.at
returns a proxy (of type std::vector<bool>::reference
) that allows you to write the element. If you want to check if it returns a non-const reference, you need to check that, not whether you can assign to it.
Alternatively you can tell std::vector<bool>
apart from others by comparing its reference
and value_type
member aliases, because for a "normal" vector reference
is just value_type&
, while std::vector<bool>::reference
is "special" (and reference
is what is returned from at
):
#include <vector>
#include <type_traits>
template <typename T,
typename U = void>
struct ref_is_ref_value : std::false_type {};
template <typename T>
struct ref_is_ref_value<T,std::enable_if_t< std::is_same_v< typename T::reference, typename T::value_type&>,void>>
: std::true_type {};
static_assert(!ref_is_ref_value<std::vector<bool>>::value);
static_assert(ref_is_ref_value<std::vector<int>>::value);
Of course, you could also just check if value_type
is bool
, because there is no other weird specialization of std::vector
.
CodePudding user response:
If you can use C 20, you can express this notion in a concept like so:
#include <vector>
#include <concepts>
#include <iostream>
template<class T>
concept is_ref_extractable_from_vector = requires(std::vector<T> vec)
{
{vec.front()} -> std::same_as<T&>;
};
int main()
{
std::cout << "Int extractable: " << is_ref_extractable_from_vector<int> << '\n';
std::cout << "Bool extractable: " << is_ref_extractable_from_vector<bool> << '\n';
}
Output:
Int extractable: 1Bool extractable: 0
Here the concept is_ref_extractable_from_vector<T>
has the following requirements: Given an std::vector<T>
named vec
, vec.front()
must compile and the return value of that expression must be the same as a T&
.
Then you could use it in your struct like so:
template<typename T>
struct GenericContainer
{
T& at(size_t index) requires is_ref_extractable_from_vector<T>
{
return data.at(index);
}
void append(const T& value)
{
data.push_back(value);
}
std::vector<T> data;
};
Now if you try to call GenericContainer<bool>::at
, it will refuse to compile with an error message mentioning the constraint is_ref_extractable_from_vector
but you can still use other GenericContainer<bool>
functionality.
CodePudding user response:
Your traits will not work because std::declval<bool&>() = std::declval<std::vector<bool>>().front())
is still well-formed even if the latter is an rvalue.
You can determine whether T&
can be bound to vector<T>.front()
by defining a help function that only accepts lvalue reference:
template <class U>
static void fun(U&);
With this we can define our trait like:
template <typename T>
struct is_ref_extractable_from_vector
{
private:
template <class U>
static void fun(U&);
template <class U, class ENABLED = decltype(fun<U>(std::declval<std::vector<U>>().front()))>
static std::true_type try_assignment(U&&);
static std::false_type try_assignment(...);
public:
using type = decltype(try_assignment(std::declval<T>()));
};