C++20 Concepts and Templates: fixe-size and resizable array

Viewed 517

I recently started to learn the new features introduced in C++20. I have some trouble to implement concepts.

Toy examples: I would like to define a concept representing a resizable array:

template<class T>
concept ResizableArray= std::is_same<std::vector<double>, T>::value;

Such code compiles under Visual Studio and works well. However, I am stuck writing a concept representing a fixed-size array of any size (std::array<double, 1>, std::array<double, 2> ... should be valid). My attempt:

template<template<unsigned int> class T, unsigned int S>
concept FixedArray = std::is_same<std::array<double, S>, T<double, S>>::value;

does not compile!

Questions:

  1. Where is the mistake?
  2. How can I write a concept that represents a fixed-size array or a resizable array? My attempt
template<template<unsigned int> class T, unsigned int S>
concept Array= std::is_same<std::vector<double>, T>::value || std::is_same<std::array<double, S>, T<S>>::value;

does not compile!

Any suggestions would be appreciated.

2 Answers

I think you are approaching concepts in the wrong way. If you want strongly typed flags for certain types, template class specialization or constant expression aliases would do fine as it has done for the past:

template<class A>
struct is_fixed_array : std::false_type {};

// only works with arrays by specialization.
template<class T, std::size_t I>
struct is_fixed_array<std::array<T,I>> : std::true_type {};

template<class T>
constexpr bool is_fixed_array_v = is_fixed_array<T>::value;

Instead, with concepts, you would like to inspect the object interface regardless of the actual type you have and avoiding strong comparisons as you are doing with std::is_same<>. Here a small example illustrating what I mean:

template<class T>
concept ResizableArray = requires(T container)
{
  // I only accept types that have a method resize(std::size_t)
  container.resize(std::size_t{0});
  // further things that tells you is an array...
};

template<class T>
concept FixedArray = requires(T container)
{
  // I only accept types that are specialized with tuple_size
  std::tuple_size_v<T> -> std::convertible_to<std::size_t>;
  // further things that tells you is an array...
};

// class with exactly the same interface as array
template<class T, std::size_t I>
struct MyArray : std::array<T,I> {};

static_assert(is_fixed_array_v<std::array<int,1>>);
// static_assert(is_fixed_array_v<MyArray<int,1>>); // assertion fails!

// instead, concepts work with any type that fullfil the defined interface
static_assert(ResizableArray<std::vector<int>>);
static_assert(not ResizableArray<MyArray<int,1>>);
static_assert(not FixedArray<std::vector<int>>);
static_assert(FixedArray<MyArray<int,1>>);

Take a look at type T here:

template<
    /* Only one template parameter of T*/
    template<unsigned int> class T, 
    unsigned int S
> concept FixedArray = \
    std::is_same<
        std::array<double, S>, 
        /* Two parameters! */
        T<double, S>
    >::value;

You say, that T is template template parameter. It has only one non-type own template parameter of type unsigned int, but later you write T<double, S> - here T has two template params - it is a mistake.

You have to write like that:

template<
    /* Two template parameters */
    template<class, unsigned int> class T, 
    unsigned int S
> concept FixedArray = \
    std::is_same<std::array<double, S>, T<double, S>>::value;

I think a case with resizable or non-resizable array has the same problem. Try to fix your fixed-size array concept and join with your well-written resizable array concept.

UPD: Moreover, why not to write like that?

template<class T, unsigned int S> 
concept FixedArray = \
    std::is_same<std::array<double, S>, T>::value;
Related