How to test if a struct contains specific members by SFINAE - c++

In order to define overloaded functions to parse an input string to specific data type, I do like below:
#include <type_traits>
// /std:c++17 enabled
template <class T>
constexpr bool is_point_v = std::is_class_v<T> && !std::is_enum_v<T>
&& std::is_arithmetic_v<decltype(T::x)>
&& std::is_arithmetic_v<decltype(T::y)>
&& (sizeof(T) == sizeof(T::y) + sizeof(T::y));
template <class T>
constexpr bool is_rect_v = std::is_class_v<T> && !std::is_enum_v<T>
&& std::is_arithmetic_v<decltype(T::left)>
&& std::is_arithmetic_v<decltype(T::top)>
&& std::is_arithmetic_v<decltype(T::right)>
&& std::is_arithmetic_v<decltype(T::bottom)>
&& (sizeof(T) == sizeof(T::left) + sizeof(T::top) + sizeof(T::right) + sizeof(T::bottom));
template<typename T>
struct Point {
std::enable_if_t<std::is_arithmetic_v<T>, T>
x {}, y {};
Point() noexcept {}
};
template<typename T>
struct Rect {
std::enable_if_t<std::is_arithmetic_v<T>, T>
left {}, top {}, right {}, bottom {};
Rect() noexcept {}
};
class DBParser
{
public:
template <typename T>
typename std::enable_if_t<std::is_arithmetic_v<T> || std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>, T>
get(const char* key, T defaultValue = {}) {
// ...
return defaultValue;
}
template <typename T>
typename std::enable_if_t<is_point_v<T>, T>
get(const char* key, T defaultValue = {}) {
// ...
return defaultValue;
}
template <typename T>
typename std::enable_if_t<is_rect_v<T>, T>
get(const char* key, T defaultValue = {}) {
// ...
return defaultValue;
}
};
int main()
{
DBParser db;
auto point1 = db.get<Point<int>>("point1", {});
auto point2 = db.get<Point<float>>("point2", {});
auto rect1 = db.get<Rect<int>>("rect1", {});
auto rect2 = db.get<Rect<float>>("rect2", {});
auto ret3 = db.get<int>("key3", {});
}
Compiling the code above gives a tone of errors. Obviously the SFINAE I did for the functions "get" are incorrect and somewhat clumsy but I could not figure it out.
Could you give me some fixes for the code? In the case C++17 could not make the code less verbose, I accept C++20.
UPDATE:
In order to make the question is more appropriate to the issue, I want to change the title and add more information.
One thing strange is if I put the testing of struct directly to the functions like this:
template <class T>
std::enable_if_t < std::is_class_v<T> && !std::is_enum_v<T>
&& std::is_arithmetic_v<decltype(T::x)>
&& std::is_arithmetic_v<decltype(T::y)>
&& (sizeof(T) == sizeof(decltype(T::x)) + sizeof(decltype(T::y))), T>
get(const char* key, T defaultValue) { // Point
return defaultValue;
}
It get compiled without errors. So I think the assumption from Nicol Bolas may be not correct.

Related

Generalizing std::conditional_t<>

I have a function that computes a certain object from a given parameter (say, an important node from a graph). Now, when calculating such an object, the function might allocate some memory. Sometimes I want the function to just return the result, and sometimes to return the result plus the memory used to compute it.
I typically solve this binary case like this:
enum class what {
what1, // return, e.g., just an int
what2 // return, e.g., a std::pair<int, std::vector<int>>
};
template <what w>
std::conditional_t<w == what::what1, int, std::pair<int, std::vector<int>>>
calculate_something(const param& p) { ... }
I would like to generalize the solution above to longer enumerations
enum class list_whats {
what1,
what2,
what3,
what4,
what5
};
One possible solution is to nest as many std::conditional_t as needed
template <list_whats what>
std::conditional_t<
what == list_whats::what1,
int,
std::conditional_t<
what == list_whats::what2,
float,
....
>
>
>
calculate_something(const param& p) { ... }
But this is cumbersome and perhaps not too elegant.
Does anyone know how to do this in C++ 17?
EDIT
To make the question perfectly clear: how do I implement the function return_something so as to be able to run the following main?
int main() {
int s1 = return_something<list_whats::what1>();
s1 = 3;
float s2 = return_something<list_whats::what2>();
s2 = 4.0f;
double s3 = return_something<list_whats::what3>();
s3 = 9.0;
std::string s4 = return_something<list_whats::what4>();
s4 = "qwer";
std::vector<int> s5 = return_something<list_whats::what5>();
s5[3] = 25;
}
I don't think you should use std::conditional at all to solve your problem. If I get this right, you want to use a template parameter to tell your function what to return. The elegant way to do this could look something like this:
#include <vector>
enum class what { what1, what2 };
template <what W>
auto compute() {
if constexpr (W == what::what1) {
return 100;
}
if constexpr (W == what::what2) {
return std::pair{100, std::vector<int>{}};
}
}
auto main() -> int {
[[maybe_unused]] const auto as_int = compute<what::what1>();
[[maybe_unused]] const auto as_pair = compute<what::what2>();
}
You can also use template specialization if you prefer another syntax:
template <what W>
auto compute();
template <>
auto compute<what::what1>() {
return 100;
}
template <>
auto compute<what::what2>() {
return std::pair{100, std::vector<int>{}};
}
Here's my approach:
what_pair is a pair that corresponds one enum to one type.
what_type_index accepts a enum and a std::tuple<what_pair<...>...> and searches the tuple map where the enums are equal and returns index. It returns maximum std::size_t value, if no match was found.
what_type is the final type, it is the tuple element at the found position. The program won't compile when the index is std::size_t max value because of invalid std::tuple access.
template<what W, typename T>
struct what_pair {
constexpr static what w = W;
using type = T;
};
template<what w, typename tuple_map>
constexpr auto what_type_index() {
std::size_t index = std::numeric_limits<std::size_t>::max();
auto search_map = [&]<std::size_t... Ints>(std::index_sequence<Ints...>) {
((std::tuple_element_t<Ints, tuple_map>::w == w ? (index = Ints) : 0), ...);
};
search_map(std::make_index_sequence<std::tuple_size_v<tuple_map>>());
return index;
}
template<what w, typename tuple_map>
using what_type = typename
std::tuple_element_t<what_type_index<w, tuple_map>(), tuple_map>::type;
and this is the example usage:
int main() {
using what_map = std::tuple<
what_pair<what::what1, int>,
what_pair<what::what2, float>,
what_pair<what::what3, double>,
what_pair<what::what4, std::string>,
what_pair<what::what5, std::vector<int>>>;
static_assert(std::is_same_v<what_type<what::what1, what_map>, int>);
static_assert(std::is_same_v<what_type<what::what2, what_map>, float>);
static_assert(std::is_same_v<what_type<what::what3, what_map>, double>);
static_assert(std::is_same_v<what_type<what::what4, what_map>, std::string>);
static_assert(std::is_same_v<what_type<what::what5, what_map>, std::vector<int>>);
//compilation error, because 'what6' wasn't specified in the 'what_map'
using error = what_type<what::what6, what_map>;
}
try it out on godbolt.
I found a possible solution that nests two structs: the first takes a list of Boolean values to indicate which type should be used, and the nested struct takes the list of possible types (see conditional_list in the example code below -- the nested structs were inspired by this answer). But perhaps it's not elegant enough. I'm wondering if there is a possible solution of the form
std::conditional_list<
..., // list of Boolean values, (of any length!)
... // list of types (list that should be as long as the first)
>::type
My proposal
#include <type_traits>
#include <iostream>
#include <vector>
// -----------------------------------------------------------------------------
template<auto A, auto... ARGS>
constexpr auto sum = A + sum<ARGS...>;
template<auto A>
constexpr auto sum<A> = A;
// -----------------------------------------------------------------------------
template <bool... values>
static constexpr bool exactly_one_v = sum<values...> == 1;
// -----------------------------------------------------------------------------
template <bool... values>
struct which {
static_assert(exactly_one_v<values...>);
template <std::size_t idx, bool v1, bool... vs>
struct _which_impl {
static constexpr std::size_t value =
(v1 ? idx : _which_impl<idx + 1, vs...>::value);
};
template <std::size_t idx, bool v>
struct _which_impl<idx, v> {
static constexpr std::size_t value = (v ? idx : idx + 1);
};
static constexpr std::size_t value = _which_impl<0, values...>::value;
};
template <bool... conds>
static constexpr std::size_t which_v = which<conds...>::value;
// -----------------------------------------------------------------------------
template <std::size_t ith_idx, typename... Ts>
struct ith_type {
template <std::size_t cur_idx, typename t1, typename... ts>
struct _ith_type_impl {
typedef
std::conditional_t<
ith_idx == cur_idx,
t1,
typename _ith_type_impl<cur_idx + 1, ts...>::type
>
type;
};
template <std::size_t cur_idx, typename t1>
struct _ith_type_impl<cur_idx, t1> {
typedef
std::conditional_t<ith_idx == cur_idx, t1, std::nullptr_t>
type;
};
static_assert(ith_idx < sizeof...(Ts));
typedef typename _ith_type_impl<0, Ts...>::type type;
};
template <std::size_t ith_idx, typename... ts>
using ith_type_t = typename ith_type<ith_idx, ts...>::type;
// -----------------------------------------------------------------------------
template <bool... conds>
struct conditional_list {
template <typename... ts>
struct good_type {
static_assert(sizeof...(conds) == sizeof...(ts));
typedef ith_type_t<which_v<conds...>, ts...> type;
};
};
// -----------------------------------------------------------------------------
enum class list_whats {
what1,
what2,
what3,
what4,
what5,
};
template <list_whats what>
typename conditional_list<
what == list_whats::what1,
what == list_whats::what2,
what == list_whats::what3,
what == list_whats::what4,
what == list_whats::what5
>::template good_type<
int,
float,
double,
std::string,
std::vector<int>
>::type
return_something() noexcept {
if constexpr (what == list_whats::what1) { return 1; }
if constexpr (what == list_whats::what2) { return 2.0f; }
if constexpr (what == list_whats::what3) { return 3.0; }
if constexpr (what == list_whats::what4) { return "42"; }
if constexpr (what == list_whats::what5) { return {1,2,3,4,5}; }
}
int main() {
auto s1 = return_something<list_whats::what1>();
s1 = 3;
auto s2 = return_something<list_whats::what2>();
s2 = 4.0f;
auto s3 = return_something<list_whats::what3>();
s3 = 9.0;
auto s4 = return_something<list_whats::what4>();
s4 = "qwer";
auto s5 = return_something<list_whats::what5>();
s5[3] = 25;
}
An alternative to working only with types is to write a function that returns the specific type, or an identity<type>. It's sometimes more readable. Here is an example:
// if you don't have it in std
template<typename T>
struct identity {
using type = T;
};
enum class what {
what1,
what2,
what3
};
template<what w>
auto return_type_for_calc() {
if constexpr (w == what::what1) {
return identity<int>();
} else if constexpr (w==what::what2) {
return identity<double>();
} else {
return identity<float>();
}
}
template<what w>
decltype(return_type_for_calc<w>())
calculate_something()
{
return {};
}
int main() {
calculate_something<what::what1>();
calculate_something<what::what2>();
return 0;
}
Although I already posted an answer to my own question, and I accepted an answer from another user, I thought I could post another possibility in tackling this problem using the following struct
template <typename... Ts> struct type_sequence { };
which I learnt about in this talk by Andrei Alexandrescu. Since I learnt quite a bit by using it and the result is a bit simpler than the original answer that used two nested structs I thought I would share it here. However, the solution I would actually implement is the one that was accepted.
This is the full code with a main function included. Notice the change in the specification of function return_something. Now this function indicates the return type (which I like very much, perhaps I'm old fashioned) in a more readable way than in my first answer. You can try it out here.
#include <type_traits>
#include <iostream>
#include <vector>
template <bool... values>
struct which {
template <std::size_t idx, bool v1, bool... vs>
struct _which_impl {
static constexpr std::size_t value =
(v1 ? idx : _which_impl<idx + 1, vs...>::value);
};
template <std::size_t idx, bool v>
struct _which_impl<idx, v> {
static constexpr std::size_t value = (v ? idx : idx + 1);
};
static constexpr std::size_t value = _which_impl<0, values...>::value;
};
template <std::size_t ith_idx, typename... Ts>
struct ith_type {
template <std::size_t cur_idx, typename t1, typename... ts>
struct _ith_type_impl {
using type =
std::conditional_t<
ith_idx == cur_idx,
t1,
typename _ith_type_impl<cur_idx + 1, ts...>::type
>;
};
template <std::size_t cur_idx, typename t1>
struct _ith_type_impl<cur_idx, t1> {
using type = std::conditional_t<ith_idx == cur_idx, t1, std::nullptr_t>;
};
using type = typename _ith_type_impl<0, Ts...>::type;
};
template <std::size_t ith_idx, typename... ts>
using ith_type_t = typename ith_type<ith_idx, ts...>::type;
template <bool... conds>
static constexpr std::size_t which_v = which<conds...>::value;
template <typename... Ts> struct type_sequence { };
template <bool... values> struct bool_sequence {
static constexpr std::size_t which = which_v<values...>;
};
template <std::size_t ith_idx, typename... Ts>
struct ith_type<ith_idx, type_sequence<Ts...>> : ith_type<ith_idx, Ts...>
{ };
template <typename bool_sequence, typename type_sequence>
struct conditional_list {
using type = ith_type_t<bool_sequence::which, type_sequence>;
};
template <typename bool_sequence, typename type_sequence>
using conditional_list_t =
typename conditional_list<bool_sequence, type_sequence>::type;
enum class list_whats {
what1,
what2,
what3,
what4,
what5,
};
template <list_whats what>
conditional_list_t<
bool_sequence<
what == list_whats::what1,
what == list_whats::what2,
what == list_whats::what3,
what == list_whats::what4,
what == list_whats::what5
>,
type_sequence<
int,
float,
double,
std::string,
std::vector<int>
>
>
return_something() noexcept {
if constexpr (what == list_whats::what1) { return 1; }
if constexpr (what == list_whats::what2) { return 2.0f; }
if constexpr (what == list_whats::what3) { return 3.0; }
if constexpr (what == list_whats::what4) { return "42"; }
if constexpr (what == list_whats::what5) { return {1,2,3,4,5}; }
}
int main() {
[[maybe_unused]] auto s1 = return_something<list_whats::what1>();
[[maybe_unused]] auto s2 = return_something<list_whats::what2>();
[[maybe_unused]] auto s3 = return_something<list_whats::what3>();
[[maybe_unused]] auto s4 = return_something<list_whats::what4>();
[[maybe_unused]] auto s5 = return_something<list_whats::what5>();
}

Error: Variadic template class has incomplete type

I have the code:
#include <unordered_set>
template<typename First, typename Enable = void, typename ... T>
class converged_is_exactly_equal_functor;
template<typename ... T>
bool converged_is_exactly_equal(const T& ...);
template <typename T, typename std::enable_if<std::is_arithmetic<T>::value || std::is_enum<T>::value>::type* = nullptr>
bool is_exactly_equal(const T other, const T one) {
return (other == one);
}
template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>
{
private:
static std::unordered_set<First*> visited_values;
void visit_value (const First& value_to_visit) {
visited_values.insert(&value_to_visit);
}
bool is_visited (const First& value_to_check) {
return (visited_values.find(&value_to_check) != visited_values.end());
}
public:
converged_is_exactly_equal_functor(void){}
bool operator () (const First& first_arg, const T& ... expanded_args) const {
if (!is_visited(first_arg)) {
visit_value(first_arg);
return is_exactly_equal(first_arg, expanded_args ...);
}
return true;
}
};
template<typename First, typename ... T>
std::unordered_set<First*> converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>::visited_values;
template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) != 1>, T ...>
{
public:
converged_is_exactly_equal_functor(void){}
bool operator () (const First& first_arg, const T& ... expanded_args) const {
return is_exactly_equal(first_arg, expanded_args ...);
}
};
template<typename ... T>
bool converged_is_exactly_equal(const T& ... expanded_args) {
converged_is_exactly_equal_functor<T ... > my_functor;
return my_functor(expanded_args ...);
}
class a {
public:
a() : dbid(1), lsb(123) {}
int dbid;
long lsb;
};
bool operator == (const a& other, const a& one) {
if (&other == &one)
return true;
return (
converged_is_exactly_equal(other.dbid, one.dbid) &&
converged_is_exactly_equal(other.lsb, one.lsb)
);
}
int main(void) {
a as, bs;
as == bs;
}
Given that class a is simple group of primitive types, why am I receiving the following error:
my_file.cxx: In instantiation of 'bool converged_is_exactly_equal(const T& ...) [with T = {long int, long int}]':
my_file.cxx:690:56: required from here
my_file.cxx:682:48: error: 'converged_is_exactly_equal_functor<long int, long int> my_functor' has incomplete type
converged_is_exactly_equal_functor<T ... > my_functor;
I believe the error has nothing to do with the proprietary data structures, but I don't see why the type could be incomplete. I have the header file for unordered_set included in this same file.
All definitions of is_exactly_equal(T) are done between the forward declarations and the templates' definitions.
Please be as explicit as possible, since I tend to find it complicated to understand template errors in general.
I can provide any more information necessary, but I'll only be back tomorrow. (This one has drained me out :-/)
The problem is in the converged_is_exactly_equal_functor class and in it's use.
You declare it as follows
template<typename First, typename Enable = void, typename ... T>
class converged_is_exactly_equal_functor;
without defining it.
Then you define two specializations: one for the case sizeof...(T) == 1
template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) == 1>, T ...>
{
// ...
};
and one for the case sizeof...(T) != 1
template<typename First, typename ... T>
class converged_is_exactly_equal_functor<First, std::enable_if<sizeof...(T) != 1>, T ...>
{
// ...
};
You use the class inside converged_is_exactly_equal
template<typename ... T>
bool converged_is_exactly_equal(const T& ... expanded_args) {
converged_is_exactly_equal_functor<T ... > my_functor;
return my_functor(expanded_args ...);
}
that is called, in your programs, two times
converged_is_exactly_equal(other.dbid, one.dbid) &&
converged_is_exactly_equal(other.lsb, one.lsb)
The first time with two int, the second time with two long.
In both case, you declare a converged_is_exactly_equal_functor value with a second template parameter that isn't void, so doesn't matches the specialization, so matches the main template but the main template is declared but not defined.
So the error.
A possible solution: throw away the SFINAE part and simply declare/define the class for a variadic list of following types (the old sizeof...(T) != 0)
template <typename F, typename ... Ts>
class converged_is_exactly_equal_functor
{
public:
converged_is_exactly_equal_functor ()
{ }
bool operator () (F const & fa, Ts const & ... ea) const
{ return is_exactly_equal(fa, ea ...); }
};
and define a specialization for two cases only (the old sizeof...(T) == 1 case)
template <typename First, typename Second>
class converged_is_exactly_equal_functor<First, Second>
{
private:
static std::unordered_set<First const *> visited_values;
void visit_value (First const & value_to_visit) const
{ visited_values.insert(&value_to_visit); }
bool is_visited (First const & value_to_check) const
{ return (visited_values.find(&value_to_check) != visited_values.end()); }
public:
converged_is_exactly_equal_functor ()
{ }
bool operator () (First const & fa, Second const & sa) const
{
if ( false == is_visited(fa) )
{
visit_value(fa);
return is_exactly_equal(fa, sa);
}
return true;
}
};
template <typename First, typename Second>
std::unordered_set<First const *> converged_is_exactly_equal_functor<First, Second>::visited_values;
Observe that I've added some const to let it compile.

Template function to extract value out of several nested unordered_map's

Let's assume I have a nested std::unordered_map that looks like this:
std::unordered_map<ResourceName, std::unordered_map<HAL::ResourceFormat::Color, HAL::RTDescriptor>>
I want a function that will return a pointer to HAL::RTDescriptor based on two keys ResourceName and HAL::ResourceFormat::Color if object is present or nullptr otherwise. The straightforward implementation looks like this:
const HAL::RTDescriptor* ResourceDescriptorStorage::GetRTDescriptor(ResourceName resourceName, HAL::ResourceFormat::Color format) const
{
auto mapIt = mRTDescriptorMap.find(resourceName);
if (mapIt == mRTDescriptorMap.end()) {
return nullptr;
}
auto& nestedMap = mapIt->second;
auto nestedMapIt = nestedMap.find(format);
if (nestedMapIt == nestedMap.end()) {
return nullptr;
}
return &nestedMapIt->second;
}
Is there a way to use templates to generalize the logic?
Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:
template<
template<class...> class AssociativeContainer,
class... Keys
>
decltype(auto) Find(const AssociativeContainer<...>& rootContainer, Keys&&... keys)
{
...
}
Simpler solution (requires C++17):
template<class AssociativeContainer, class Key, class... Keys>
auto Find(const AssociativeContainer& container, Key&& key, Keys&&... keys){
auto it = container.find(std::forward<Key>(key));
bool found = it != container.end();
if constexpr(sizeof...(Keys) == 0)
return found ? &it->second : nullptr;
else
return found ? Find(it->second, std::forward<Keys>(keys)...) : nullptr;
}
This also allows to get a reference to any inbetween container, as it doesn't require to pass all keys.
Is there a way to use templates to generalize the logic? Something with parameter packs for keys. Something that will go through each nested container, check for object availability and return it or nullptr at the end:
Require a little of work (maybe someone more expert than me can make it simpler) but sure it's possible.
By example... given a custom type traits (and using to simplify the use) as follows
template <typename T>
struct lastType
{ using type = T; };
template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
{ };
template <typename T>
using lastType_t = typename lastType<T>::type;
you can write Find() recursively as follows
// ground case
template <typename V>
V const * Find (V const & val)
{ return &val; }
// recursion case
template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
if ( mapIt == cnt.cend() )
return nullptr;
return Find(mapIt->second, std::forward<Ks>(keys)...);
}
The following is a full compiling example
#include <map>
#include <string>
#include <iostream>
#include <unordered_map>
template <typename T>
struct lastType
{ using type = T; };
template <template <typename...> class C, typename K, typename V>
struct lastType<C<K, V>> : public lastType<V>
{ };
template <typename T>
using lastType_t = typename lastType<T>::type;
template <typename V>
V const * Find (V const & val)
{ return &val; }
template <typename C, typename K0, typename ... Ks>
lastType_t<C> const * Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
if ( mapIt == cnt.cend() )
return nullptr;
return Find(mapIt->second, std::forward<Ks>(keys)...);
}
using typeC = std::map<int,
std::unordered_map<std::string,
std::unordered_map<long,
std::map<char, long long>>>>;
int main ()
{
typeC c;
c[0]["one"][2l]['3'] = 4ll;
auto v = Find(c, 0, "one", 2l, '3');
std::cout << (*v) << std::endl;
static_assert( std::is_same_v<decltype(v), long long const *>, "!" );
}
-- EDIT --
I'm particularly dumb today: as highlighted by krisz in his answer (thanks), the ternary operator permit the use of auto as return type (from C++14).
So there is no needs of the lastType custom type traits and Find() can be simply written as
// ground case
template <typename V>
V const * Find (V const & val)
{ return &val; }
// recursion case
template <typename C, typename K0, typename ... Ks>
auto Find (C const & cnt, K0 && key0, Ks && ... keys)
{
auto mapIt = cnt.find(std::forward<K0>(key0));
return mapIt == cnt.cend()
? nullptr
: Find(mapIt->second, std::forward<Ks>(keys)...);
}
For C++11, the recursion case require also the trailing return type; by example
-> decltype(Find(cnt.find(std::forward<K0>(key0))->second, std::forward<Ks>(keys)...))

How to write a simple range concept?

How to write a concept that will describe the types the Range-based for loop is enabled for?
One attempt is:
template < typename Range > concept bool RRange
= requires(Range range) {{std::begin(range),std::end(range)};};
but what I really want is some thing like this:
template < typename Range > concept bool RRange
= requires(Range range) {{for(auto&& item : range);};}; // compile error
that is, RRange to be the concept of all types the expression for(auto&& item : range); is valid for. What is the best way to achieve this?
I am using GCC7 snapshot with g++ -std=c++1z -fconcepts.
Here's what I came up with while reviewing [stmt.ranged].
#include <utility>
#include <experimental/type_traits>
template <class T> using begin_non_mf_t = decltype(begin(std::declval<T>()));
template <class T> using begin_mf_t = decltype(std::declval<T>().begin());
template <class T> using begin_t = decltype(T::begin);
template <class T> using end_non_mf_t = decltype(end(std::declval<T>()));
template <class T> using end_mf_t = decltype(std::declval<T>().end());
template <class T> using end_t = decltype(T::end);
template <class T>
constexpr bool has_member_begin_or_end {
std::experimental::is_detected_v<begin_mf_t,T> ||
std::experimental::is_detected_v<begin_t,T> ||
std::experimental::is_detected_v<end_mf_t,T> ||
std::experimental::is_detected_v<end_t,T>};
template <class T>
std::add_lvalue_reference_t<T> declref() noexcept;
template <class T> using declref_t = decltype(declref<T>());
template <class T>
concept bool Range =
requires /*Arrays*/ {
requires std::is_array_v<T>;
requires std::extent_v<T>!=0; // Extent is known.
} ||
/*Classes with member begin/end*/
requires {
requires std::is_class_v<T> && has_member_begin_or_end<T>;
} &&
requires (begin_mf_t<declref_t<T>> _begin,
end_mf_t<declref_t<T>> _end) {
{ _begin!=_end } -> bool;
{ *_begin } -> auto&&;
{ ++_begin };
} ||
/*Types with non-member begin/end*/
requires {
requires !std::is_class_v<T> || !has_member_begin_or_end<T>;
} &&
requires (begin_non_mf_t<declref_t<T>> _begin,
end_non_mf_t<declref_t<T>> _end) {
{ _begin!=_end } -> bool;
{ *_begin } -> auto&&;
{ ++_begin };
};
And the test cases.
#include <vector>
// Evaluates to true or diagnoses which constraints failed.
template <Range> constexpr bool is_range {true};
static_assert(!Range<void>);
static_assert(!Range<int>);
static_assert(!Range<int*>);
static_assert(!Range<int[]>);
static_assert(is_range<int[1]>);
static_assert(is_range<std::vector<int>>);
struct A { };
struct B {
int begin;
};
struct C {
int* begin();
int* end();
};
struct D { };
struct E {
int end;
};
enum F { };
struct G {
int* begin() &&;
int* end();
};
struct H {
int* begin() &&;
int* end() &&;
};
int* begin(D);
int* end(D);
int* begin(E);
int* end(E);
int* begin(F);
int* end(F);
int* begin(H);
int* end(H);
static_assert(!Range<A>);
static_assert(!Range<B>);
static_assert(is_range<C>);
static_assert(is_range<D>);
static_assert(!Range<E>);
static_assert(is_range<F>);
static_assert(!Range<G>);
static_assert(!Range<H>);
int main() { }
According to P0587, this should suffice:
#include <vector>
template<typename T>
concept bool RangeForAble = requires (T t) {
requires requires (decltype(begin(t)) b, decltype(end(t)) e) {
b != e;
++b;
*b;
};
};
int main()
{
static_assert(RangeForAble<std::vector<int>>);
static_assert(RangeForAble<double>);
}
In C++20 it will look something like this:
template< class T >
concept RealContainer = requires(T&& t) {
std::begin(std::forward<T>(t));
std::end (std::forward<T>(t));
};
Probably not perfect, but works for std::vector and C array, demo: https://gcc.godbolt.org/z/M4xhnqG46

boost::variant comparison with contained value

I am trying to find a way to compare boost::variant with underlying value without constructing variant from this underlying value. The question is defined in the comment in "main()" function
And auxiliary question is about the comparison operators defined in the code. How to decrease the # of comparison operators? If boost::variant contains, say, 6 different types, do I have to define 6! operators to be able to compare two variants?
Thanks!
#include <boost/variant.hpp>
namespace test {
namespace Tag {
struct Level1{ int t{ 1 }; };
struct Level2{ int t{ 2 }; };
}
template <typename Kind> struct Node;
using LevelOne = Node<Tag::Level1>;
using LevelTwo = Node<Tag::Level2>;
using VariantNode = boost::variant
<
boost::recursive_wrapper<LevelOne>,
boost::recursive_wrapper<LevelTwo>
>;
typedef VariantNode* pTree;
typedef std::vector<pTree> lstTree;
template <typename Kind> struct Node
{
Node(pTree p, std::string n) : parent(p), name(n) {}
Node(const Node& another) : name(another.name), parent(another.parent) {}
virtual ~Node() {}
std::string name;
pTree parent;
};
bool operator == (const LevelOne& one, const LevelTwo& two) {
return false;
}
bool operator == (const LevelTwo& two, const LevelOne& one) {
return false;
}
bool operator == (const LevelOne& one, const LevelOne& two) {
return true;
}
bool operator == (const LevelTwo& one, const LevelTwo& two) {
return true;
}
}
int main(int argc, char *argv[])
{
using namespace test;
LevelOne l1(nullptr, "level one");
VariantNode tl2 = VariantNode(LevelTwo(nullptr, "level two"));
VariantNode tl1 = VariantNode(LevelOne(nullptr, "level one"));
bool rv = (tl1 == tl2); // this line compiles OK (comparing two variants)
// comparison below does not compile, because "l1" is not a variant.
// Question: How can I compare "variant" value "tl1"
// with one of the possible content values "l1"
bool rv1 = (tl1 == l1);
return 1;
}
The following will work with any number of types in the variant:
template<typename T>
struct equality_visitor : boost::static_visitor<bool> {
explicit constexpr equality_visitor(T const& t) noexcept : t_{ &t } { }
template<typename U, std::enable_if_t<std::is_same<T, U>::value>* = nullptr>
constexpr bool operator ()(U const& u) const {
return *t_ == u;
}
template<typename U, std::enable_if_t<!std::is_same<T, U>::value>* = nullptr>
constexpr bool operator ()(U const&) const {
return false;
}
private:
T const* t_;
};
template<
typename T,
typename... Ts,
typename = std::enable_if_t<
boost::mpl::contains<typename boost::variant<Ts...>::types, T>::value
>
>
bool operator ==(T const& t, boost::variant<Ts...> const& v) {
equality_visitor<T> ev{ t };
return v.apply_visitor(ev);
}
template<
typename T,
typename... Ts,
typename = std::enable_if_t<
boost::mpl::contains<typename boost::variant<Ts...>::types, T>::value
>
>
bool operator !=(T const& t, boost::variant<Ts...> const& v) {
return !(t == v);
}
The catch is that comparisons must always be of the form value == variant or value != variant rather than variant == value or variant != value. This is because boost::variant<> itself defines these operators to always static_assert, and there is no way for us to make a global operator more specialized than variant<>'s built-in ones.
Online Demo