Continuing with my studies on C++ 20 concepts, I am trying to write a concept
that would assure that every type in a std::tuple
has inner type defined with a specific name, and that all the inner
s have the same type.
I tried this:
#include <concepts>
#include <string>
#include <tuple>
template <typename t>
concept type_model = requires(t p_t) {
typename t::inner;
};
template <typename t, typename u>
concept same_inner = requires(t p_t, u p_u) {
std::is_same_v<typename t::inner, typename u::inner>;
};
template <typename t, typename... t_type_model>
concept container_model = requires(t p_t) {
requires((same_inner<t_type_model,
std::tuple_element_t<0, std::tuple<t_type_model...>>> &&
...));
};
struct type_1 {
using inner = int;
};
struct type_2 {
using inner = std::string;
};
template <type_model... t_type_models> struct container {};
template <type_model... t_type_model>
void f(container_model<t_type_model...> auto) {}
using containers_X = container<type_1, type_2>;
int main() {
containers_X _x;
f(_x);
return 0;
}
I expected an error of concept not satisfied because type_1::inner
and type_2::inner
have different types, but no errors were reported.
This code,
template <typename t, typename... t_type_model>
concept container_model = requires(t p_t) {
requires((same_inner<t_type_model,
std::tuple_element_t<0, std::tuple<t_type_model...>>> &&
...));
};
does not actually test the condition within the second requires clause.
The correct formulation is
template <typename t, typename u>
concept same_inner = std::is_same_v<typename t::inner, typename u::inner>;
or,
template <typename t, typename u>
concept same_inner = requires (t i, u j) {
requires (std::same_as<typename t::inner, typename u::inner>);
};
This has tripped me up in the past.
Update
The container_model
concept
template <typename t, typename... t_type_model>
concept container_model = requires(t p_t) {
requires((same_inner<t_type_model,
std::tuple_element_t<0, std::tuple<t_type_model...>>> &&
...));
};
need to be something like
template <typename t, typename... t_type_model>
concept container_model = (same_inner<t, t_type_model> && ...);
At this point, the following assertions should hold:
static_assert(not same_inner<type_1, type_2>);
static_assert(not container_model<type_1, type_2>);
When the function f
is invoked, it is passed container<type_1, type_2>
which qualifies as a container_model
because it gets translated to container_model<container<type_t, type_2>>
which returns true
.
One possible fix is to change f
to something like
template <type_model... t_type_model>
void f(container_model<container<t_type_model...>> auto) {}
Just to be clear, here is all the code with changes and this code fails to compile because the container_model
constraints are not satisfied.
#include <concepts>
#include <string>
#include <tuple>
template <typename t>
concept type_model = requires(t p_t) {
typename t::inner;
};
template <typename t, typename u>
concept same_inner = std::is_same_v<typename std::decay_t<t>::inner,
typename std::decay_t<u>::inner>;
template <typename t, typename... t_type_model>
concept container_model = (same_inner<t, t_type_model> && ...);
struct type_1 {
using inner = int;
};
struct type_2 {
using inner = std::string;
};
template <type_model... t_type_models> struct container {};
template <type_model... t_type_model>
void f(container_model<container<t_type_model...>> auto) {}
using containers_X = container<type_1, type_2>;
static_assert(not same_inner<type_1, type_2>);
static_assert(not container_model<type_1, type_2>);
int main() {
containers_X _x;
f(_x);
return 0;
}
The previous update was incomplete at best. Here is the latest code which I believe enforces the desired concepts.
#include <concepts>
#include <string>
#include <tuple>
// Does T have an inner type named `inner`.
template<class T>
concept Inner = requires(T x) {
typename T::inner;
};
// Do T and U have same inner type.
template<class T, class U>
concept SameInner = Inner<T> and Inner<U> and std::is_same_v<typename T::inner, typename U::inner>;
// Helper for checking that all template parameters satisfy Inner
// concept and all pairs satisfy SameInner.
template<typename>
struct inner_container_impl : std::false_type {};
template<template<typename...> class Tp, Inner T>
struct inner_container_impl<Tp<T>> {
static constexpr bool value = true;
};
template<template<typename...> class Tp, Inner T, Inner... Ts>
struct inner_container_impl<Tp<T, Ts...>> {
static constexpr bool value = (SameInner<T, Ts> and ...);
};
// The concept just use the helper.
template<class T>
concept InnerContainer = inner_container_impl<T>::value;
// Test types.
struct type_1 {
using inner = int;
};
struct type_2 {
using inner = std::string;
};
struct type_3 {
using inner = int;
};
template<Inner... Ts> struct container {};
void f(InnerContainer auto) {}
using containers_X = std::tuple<type_1, type_2>;
using containers_Y = std::tuple<type_1, type_3>;
static_assert(not SameInner<type_1, type_2>);
static_assert(not InnerContainer<std::tuple<type_1, type_2>>);
static_assert(not InnerContainer<containers_X>);
int main() {
containers_X _x;
f(_x);
containers_Y _y;
f(_y);
return 0;
}
/work/so/scratch/src/p4.cpp:64:5: error: no matching
function for call to 'f'
f(_x);
^
/work/so/scratch/src/p4.cpp:52:6: note: candidate
template ignored: constraints not satisfied [with auto:1 = std::tuple<type_1,
type_2>]
void f(InnerContainer auto) {}
^
/work/so/scratch/src/p4.cpp:52:8: note: because
'std::tuple<type_1, type_2>' does not satisfy 'InnerContainer'
void f(InnerContainer auto) {}
^
/work/so/scratch/src/p4.cpp:35:26: note: because
'inner_container_impl<tuple<type_1, type_2> >::value' evaluated to false
concept InnerContainer = inner_container_impl<T>::value;
^
1 error generated.