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
Related
See the code below (also here https://www.godbolt.org/z/hvnvEv1ar). The code fails to compile if I uncomment the constraint for either rng or pair. I feel like I am missing something trivial, but I can't figure out why the constraint is not satisfied.
#include <vector>
#include <ranges>
#include <utility>
template <typename T>
struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_reference_wrapper_v = is_reference_wrapper<T>::value;
template <typename T>
concept ReferenceWrapper = is_reference_wrapper_v<T>;
template <typename T>
concept ReferenceWrapperPair = requires(const T& t) {
{ t.first } -> ReferenceWrapper;
{ t.second } -> ReferenceWrapper;
};
template <typename T>
concept ReferenceWrapperPairRange =
std::ranges::range<T> && ReferenceWrapperPair<std::ranges::range_value_t<T>>;
int main()
{
std::vector<std::pair<int, int>> v{ {1,2}, {3,4}, {5,6} };
auto fn = [](std::pair<int, int>& val) {
return std::pair{std::reference_wrapper<int>{val.first}, std::reference_wrapper<int>{val.second} };
};
/* ReferenceWrapperPairRange */ auto rng = v | std::views::transform(fn);
/* ReferenceWrapperPair */ auto pair = *(rng.begin());
ReferenceWrapper auto first = pair.first;
ReferenceWrapper auto second = pair.second;
return 0;
}
The compound requirements { expression } -> type-constraint requires that decltype((expression)) must satisfy the constraints imposed by type-constraint, since decltype((t.first)) will be treated as an ordinary lvalue expression, it will result in a const lvalue reference type.
You might want to use C++23 auto(x) to get the decay type
template <typename T>
concept ReferenceWrapperPair = requires(const T& t) {
{ auto(t.first) } -> ReferenceWrapper;
{ auto(t.second) } -> ReferenceWrapper;
};
Or change ReferenceWrapper concept to:
template <typename T>
concept ReferenceWrapper = is_reference_wrapper_v<std::remove_cvref_t<T>>;
Question to C++ template gurus:
I have created two template "policies" (not sure if this is the right term), which implement storage of some value types in a vector of either dumb or smart pointers:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
template <typename T>
class DumbPtrVec
{
std::vector<T*> m_vec;
public:
using handle = size_t;
~DumbPtrVec() {
std::for_each(begin(m_vec), end(m_vec), [](T* p){ delete p; });
}
handle AddElement(T* p) {
const handle index = m_vec.size();
m_vec.push_back(p);
return index;
}
T* GetElement(const handle& i) {
T* p = (i < m_vec.size())? m_vec[i] : nullptr;
return p;
}
};
template <typename T>
class SmartPtrVec
{
std::vector<std::shared_ptr<T>> m_vec;
public:
using handle = std::weak_ptr<T>;
handle AddElement(T* p) {
m_vec.emplace_back(p);
return m_vec.back(); // gets converted to weak_ptr
}
T* GetElement(const handle& i) {
T* p = (i.expired())? nullptr : i.lock().get();
return p;
}
};
template <typename T, template<typename> typename STORAGE>
class Storage
{
STORAGE<T> m_values;
public:
using handle = typename STORAGE<int>::handle;
handle AddValue(T* v) { return m_values.AddElement(v); }
T* GetValue(handle h) { return m_values.GetElement(h); }
};
int main()
{
constexpr int N = 13;
Storage<int, DumbPtrVec> d;
auto dh = d.AddValue(new int(N));
std::cout << *d.GetValue(dh) << " == " << N <<std::endl;
Storage<int, SmartPtrVec> s;
auto sh = s.AddValue(new int(N));
std::cout << *s.GetValue(sh) << " == " << N << std::endl;
return 0;
}
Everything works fine, so far.
Then I added a template wrapper, that replaces the element "handle" with a unique string and keeps a look-up table for converting strings back to the handles. If this class is explicitly derived from either DumbPtrVec or SmartPtrVec class, everything works, e.g. for SmartPtrVec:
template <typename T>
class StringHandleWrapper : SmartPtrVec<T>
{
using super = typename SmartPtrVec<T>;
using Str2HandleMap = std::unordered_map<std::string, typename super::handle>;
Str2HandleMap m_Name2HandleMap;
public:
using handle = std::string;
handle AddElement(T* p) {
typename super::handle elem = super::AddElement(p);
static int counter = 0;
std::string uuid = std::to_string(++counter);
m_Name2HandleMap[uuid] = elem;
return uuid;
}
T* GetElement(const handle& uuid) {
auto it = m_Name2HandleMap.find(uuid);
return (it != m_Name2HandleMap.end())? super::GetElement(it->second) : nullptr;
}
};
and successful invocation:
Storage<int, StringHandleWrapper> s;
std::string handle = s.AddValue(new int(N));
But I can't figure out how to add a second template parameter, STORAGE, to StringHandleWrapper, so that it could wrap any of DumbPtrVec or SmartPtrVec...
If I change StringHandleWrapper to:
template <typename T, template<typename> typename STORAGE>
class StringHandleWrapper : STORAGE<T>
{
using super = typename STORAGE<T>;
//... rest unchanged
then I can't figure out how to instantiate Storage class, as compiler complains about "too few template arguments":
Storage<int, StringHandleWrapper<SmartPtrVec>> s;
I hope I'm missing something simple...
Thank you for taking your time to look through my long question!
Create another level of template for partial argument application:
template <template <typename, template<typename> typename> class W,
template <typename> typename S>
struct Apply
{
template <typename T> using type = W<T, S>;
};
Then instantiate Storage like this:
Storage<int, Apply<StringHandleWrapper, SmartPtrVec>::type> s;
Just found the answer (it was indeed simple):
I needed to introduce two single-parameter templates
template<typename T> using StringDumbHandleWrapper = StringHandleWrapper<T, DumbPtrVec>;
template<typename T> using StringSmartHandleWrapper = StringHandleWrapper<T, SmartPtrVec>;
and use the new names in Storage instantiation, e.g.
Storage<int, StringDumbHandleWrapper> s;
So much for the long question... :)
Suppose that we have the vector class below which has been shortened to minimum to showcase the question.
template <typename T>
class VectorT : private std::vector<T>
{
using vec = std::vector<T>;
public:
using vec::operator[];
using vec::push_back;
using vec::at;
using vec::emplace_back;
// not sure if this is the beast way to check if my T is really a unique_ptr
template<typename Q = T>
typename Q::element_type* operator[](const size_t _Pos) const { return at(_Pos).get(); }
};
Is there any way to check if T is a unique_ptr and if yes to add an operator[] to return the unique_ptr::element_type*. At the same time though the normal operator[] should also work.
VectorT<std::unique_ptr<int>> uptr_v;
uptr_v.emplace_back(make_unique<int>(1));
//int* p1 = uptr_v[0]; // works fine if using vec::operator[]; is commented out
// then of course it wont work for the normal case
//std::cout << *p1;
VectorT<int*> v;
v.emplace_back(uptr_v[0].get());
int *p2 = v[0];
std::cout << *p2;
Any way to achieve something like that ?
Edited:
The reason I am asking for this is cause I can have say my container
class MyVec: public VectorT<std::unique_ptr<SomeClass>>
but I can also have a
class MyVecView: public VectorT<SomeClass*>
Both classes will pretty much have identical functions. So I am trying to avoid duplication by doing something like
template<typename T>
void doSomething(VectorT<T>& vec)
{
SomeClass* tmp = nullptr;
for (size_t i = 0; i < vec.size(); ++i)
{
tmp = vec[i]; // this has to work though
....
}
}
Then of course I can
MyVec::doSomething(){doSomething(*this);}
MyVecView::doSomething(){doSomething(*this);}
which of course means that the operator[] has to work for both cases
The goal here is to have only one operator[]. Techniques with more than one operator[] violate DRY (don't repeat yourself), and it is hard to avoid having a template method whose body would not compile if instantiated (which, under a strict reading of the standard, could result in your code being ill-formed).
So what I'd do is model the "turn something into a pointer" like this:
namespace details {
template<class T>
struct plain_ptr_t;
//specialzation for T*
template<class T>
struct plain_ptr_t<T*> {
T* operator()(T* t)const{return t;}
};
//specialzation for std::unique_ptr
template<class T, class D>
struct plain_ptr_t<std::unique_ptr<T,D>> {
T* operator()(std::unique_ptr<T>const& t)const{return t.get();}
};
//specialzation for std::shared_ptr
template<class T>
struct plain_ptr_t<std::shared_ptr<T>> {
T* operator()(std::shared_ptr<T>const& t)const{return t.get();}
};
}
struct plain_ptr {
template<class T>
typename std::result_of< details::plain_ptr_t<T>( T const& ) >::type
operator()( T const& t ) const {
return details::plain_ptr_t<T>{}( t );
}
};
now plain_ptr is a functor that maps smart pointers to plain pointers, and pointers to pointers.
It rejects things that aren't pointers. You could change it to just pass them through if you like, but it takes a bit of care.
We then use them to improve your operator[]:
typename std::result_of< plain_ptr(typename vec::value_type const&)>::type
operator[](size_t pos) const {
return plain_ptr{}(at(pos));
}
notice that it is no longer a template.
live example.
template<typename T> struct unique_ptr_type { };
template<typename T> struct unique_ptr_type<std::unique_ptr<T>> { using type = T; };
namespace detail {
template<typename T> std::false_type is_unique_ptr(T const&);
template<typename T> std::true_type is_unique_ptr(std::unique_ptr<T> const&);
}
template<typename T>
using is_unique_ptr = decltype(detail::is_unique_ptr(std::declval<T>()));
template<typename T>
class VectorT : std::vector<T> {
using vec = std::vector<T>;
public:
using vec::at;
using vec::emplace_back;
using vec::push_back;
template<typename Q = T,
typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
Q& operator [](std::size_t pos) { return vec::operator[](pos); }
template<typename Q = T,
typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
Q const& operator [](std::size_t pos) const { return vec::operator[](pos); }
template<typename Q = T,
typename U = typename unique_ptr_type<Q>::type>
U* operator [](std::size_t pos) { return vec::operator[](pos).get(); }
template<typename Q = T,
typename U = typename unique_ptr_type<Q>::type>
U const* operator [](std::size_t pos) const { return vec::operator[](pos).get(); }
};
Online Demo
SFINAE is used to only enable the custom operator[] if T is std::unique_ptr<T>, and to only enable std::vector<T>::operator[] otherwise.
If you insist on plain pointers you could write something like
template<class T> auto plain_ptr(T* p) { return p; }
template<class T> auto plain_ptr(std::unique_ptr<T>& p) { return p.get(); }
and then do
tmp = plain_ptr(vec[i]); // tmp will be SomeClass*
or you could have tmp local:
template<typename T>
void doSomething(VectorT<T>& vec)
{
static_assert(std::is_same<T, SomeClass*>::value ||
std::is_same<T, std::unique_ptr<SomeClass>>::value,
"doSomething expects vectors containing SomeClass!");
for (std::size_t i = 0; i < vec.size(); ++i)
{
auto tmp = vec[i];
// ... (*tmp).foo or tmp->foo ...
}
// or perhaps even
for(auto&& tmp : vec)
{
// ... again (*tmp).foo or tmp->foo ...
}
}
I want to implement a heterogeneous storage, using multiple maps having different value type. I am indexing the maps based on the value types. For that I am using typelist. This is a simplified example.
My type-list class looks like following.
namespace details
{
struct null{};
template<int N, typename E, typename T, typename ... Ts>
struct index : index<N+1, E, Ts...> {};
template<int N, typename E, typename ... Ts>
struct index<N, E, E, Ts...>
{
constexpr static int value = N;
};
template<int N, typename E>
struct index<N, E, null>
{
constexpr static int value = -1;
};
}
template<typename ... Ts>
struct typelist
{
using type = typelist<Ts...>;
template<typename T>
struct index : details::index<0, T, Ts..., details::null> {};
};
And storage class implementation following, A template class holding values of type T.
#include <iostream>
#include <tuple>
#include <typeinfo>
#include <map>
template<typename T>
struct storage_impl
{
//storage_impl(storage_impl const &) = delete;
bool try_getting(int key, T &value)
{
auto search = _storage.find(key);
if(search != _storage.end() )
{
value = search->second;
return true;
}
return false;
}
std::map<int,T> _storage;
};
And finally the storage manager looks like, it has several storage_impl instance, and I want to index on them based on type.
struct storage_manager{
// tuple storing different storage_impls
std::tuple< storage_impl<double> , storage_impl<std::string> > storages { storage_impl<double>{},storage_impl<std::string>{} };
using item_types = typelist<double,std::string>;
storage_manager(){}
~storage_manager(){}
storage_manager(storage_manager const &) = delete;
template<typename T>
bool try_getting(int key, T &value)
{
return std::get<item_types::index<T>::value>(storages).try_getting(key,value);
}
};
int main()
{
storage_manager mgr;
double val1;
std::cout<<mgr.try_getting(123,val1);
}
Every thing works till the storage_impl is copyable. But I want storage_impl to be non-copyable.
How to achieve same with my storage_impl being non-copyable.
P.S. - Did not want to use inheritance for same.
Following is my working code, I have declared cache_impls as member variables and specialized function storage_ref for every case.
I have implemented only try_get, other functions can be implemented using storage_ref.
#include <iostream>
#include <tuple>
#include <map>
#include <boost/noncopyable.hpp>
template<typename T>
struct storage_impl : public boost::noncopyable
{
storage_impl(){}
~storage_impl(){}
bool try_getting(int key, T &value)
{
auto search = _storage.find(key);
if(search != _storage.end() )
{
value = search->second;
return true;
}
return false;
}
bool try_setting(int key, T const &value)
{
auto search = _storage.insert(std::make_pair(key,value));
if(search.second == true )
{
return true;
}
return false;
}
bool exists(int key)
{
auto search = _storage.find(key);
if(search != _storage.end() )
{
return true;
}
return false;
}
std::map<int,T> _storage;
};
struct storage_manager : public boost::noncopyable
{
storage_impl<double> double_store;
storage_impl<std::string> string_store;
template<typename T>
struct item_return{ using type = storage_impl<T>; };
template<typename T>
typename item_return<T>::type storage_ref();
template<typename T>
bool try_getting(int key, T &value)
{
return storage_ref<T>().try_getting(key,value);
}
template<typename T>
bool try_setting(int key, T const &value)
{
return storage_ref<T>().try_setting(key,value);
}
template<typename T>
bool exists(int key)
{
return storage_ref<T>().exists(key);
}
storage_manager(){}
~storage_manager(){}
};
//double specialization
template<>
struct storage_manager::item_return<double>{ using type = storage_impl<double>&; };
template<>
inline storage_impl<double>& storage_manager::storage_ref<double>()
{
return double_store;
}
//std::string specialization
template<>
struct storage_manager::item_return<std::string>{ using type = storage_impl<std::string>&; };
template<>
inline storage_impl<std::string>& storage_manager::storage_ref<std::string>()
{
return string_store;
}
int main()
{
storage_manager mgr;
double val1 = 90;
std::cout<<mgr.try_getting(123,val1)<<'\n';
std::cout<<mgr.try_setting(123,val1)<<'\n';
std::cout<<mgr.exists<double>(123)<<'\n';
std::string val2;
std::cout<<mgr.try_getting(123,val2)<<'\n';
}
Looking forward for alternatives...
I have a function that currently accepts 2 vectors that can contain any plain old data ...
template <class T>
void addData(const vector<T>& yData, vector<T> xData)
{ .. }
Question:
Would it be possible to modify it to take two std::array or two std::vector, or even a combination thereof, given that these containers take a different number of template arguments?
Sure, it's just a matter of creating a suitable type trait. The example just uses a function f() with one argument but it is trivial to extend to take any number of arguments.
#include <array>
#include <vector>
#include <deque>
#include <utility>
#include <cstddef>
template <typename T>
struct is_array_or_vector {
enum { value = false };
};
template <typename T, typename A>
struct is_array_or_vector<std::vector<T, A>> {
enum { value = true };
};
template <typename T, std::size_t N>
struct is_array_or_vector<std::array<T, N>> {
enum { value = true };
};
template <typename T>
typename std::enable_if<is_array_or_vector<T>::value>::type
f(T const&)
{
}
int main()
{
f(std::vector<int>()); // OK
f(std::array<int, 17>()); // OK
f(std::deque<int>()); // ERROR
}
Why not just use this, which works with any container using random-access iterators, including plain old arrays. If you can use iteration instead of indexing, you can do away with the random-access requirement as well.
template <typename Cnt1, typename Cnt2>
void addData(const Cnt1& yData, Cnt2 xData) // is pass-by-value intended?
{
using std::begin;
using std::end;
typedef decltype(*begin(yData)) T;
const auto sizeY = end(yData) - begin(yData);
const auto sizeX = end(xData) - begin(xData);
// ...
}
C++03 version (doesn't support plain old arrays):
template <typename Cnt1, typename Cnt2>
void addData(const Cnt1& yData, Cnt2 xData) // is pass-by-value intended?
{
typedef Cnt1::value_type T;
const size_t sizeY = yData.end() - yData.begin();
const size_t sizeX = xData.end() - xData.begin();
// ...
}
An alternative solution:
#include <iostream>
#include <vector>
#include <array>
using std::vector;
using std::array;
template <typename Container>
struct container_helper; // undefined
template <typename T>
struct container_helper<vector<T>>
{
explicit container_helper(vector<T>& data)
: _data(data)
{}
T* get_data()
{ return &_data[0]; }
size_t get_size()
{ return _data.size(); }
private:
vector<T>& _data;
};
template <typename T, size_t N>
struct container_helper<array<T,N>>
{
explicit container_helper(array<T,N>& data)
: _data(data)
{}
T* get_data()
{ return &_data[0]; }
size_t get_size()
{ return N; }
private:
array<T,N>& _data;
};
template <typename Container1, typename Container2>
void add_data(Container1& c1, Container2& c2)
{
container_helper<Container1> c1_helper(c1);
container_helper<Container2> c2_helper(c2);
/* do whatever you want with the containers */
std::cout << "c1 size " << c1_helper.get_size() << std::endl;
std::cout << "c2 size " << c2_helper.get_size() << std::endl;
}
int main()
{
vector<int > v_ints(3);
array<int, 2> a_ints;
add_data(v_ints, a_ints);
}