Home > Mobile >  enable a template if i can get a non const reference from std::vector.front()
enable a template if i can get a non const reference from std::vector.front()

Time:10-07

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: 1

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

Demo.

  • Related