Expand parameter pack into tuple with tuple_cat - c++

Godbolt link: https://godbolt.org/z/18nseEn4G
I have a std::map of various types of vectors (cast to void*) and a T& get<T> method that gives me a reference to an element in one of the vectors in the map.
class Container {
public:
Container() {
auto v1 = new std::vector<int>({1, 2, 3, 4, 5});
auto v2 = new std::vector<char>({'a','b','c','d','e'});
auto v3 = new std::vector<double>({1.12, 2.34, 3.134, 4.51, 5.101});
items.insert({
std::type_index(typeid(std::vector<int>)),
reinterpret_cast<void*>(v1)
});
items.insert({
std::type_index(typeid(std::vector<char>)),
reinterpret_cast<void*>(v2)
});
items.insert({
std::type_index(typeid(std::vector<double>)),
reinterpret_cast<void*>(v3)
});
}
template<typename T>
T& get(int index) {
auto idx = std::type_index(typeid(std::vector<T>));
auto ptr = items.at(idx);
auto vec = reinterpret_cast<std::vector<T>*>(ptr);
return (*vec)[index];
}
private:
std::map<std::type_index, void*> items {};
};
I want to be able to use structured binding to get back references to 3 elements all at the same index but in difference vectors, but I'm not sure how to create a tuple with multiple calls to the T& get<T> method.
Something like this;
auto [a, b, c] = myContainer.get_all<int, char, double>(1); // get a reference to an int, a char, and a double from myContainer at index 1.
I'm currently trying to make use of repeated calls to T& get<T> for each parameter in a parameter pack, but I can't figure out the correct syntax.
template<typename... Ts>
auto get_all(int index) {
return std::tuple_cat<Ts...>(
std::make_tuple<Ts>(get<Ts>(index)...)
);
How could I make this work?
Here is a link to my current attempt:
https://godbolt.org/z/18nseEn4G
Alternatively, is there a "better way" to achieve this?

I would suggest using type erasure. Here is an example:
#include <vector>
#include <typeindex>
#include <memory>
#include <any>
#include <unordered_map>
#include <iostream>
#include <experimental/propagate_const>
// If no library implementation is availble, one may be copied from libstdc++
template<class T>
using propagate_const = std::experimental::propagate_const<T>;
class Container
{
public:
Container() {
std::unique_ptr<Eraser> v1{ static_cast<Eraser*>(new ErasedVector<int>(1, 2, 3, 4, 5)) };
std::unique_ptr<Eraser> v2{ static_cast<Eraser*>(new ErasedVector<char>('a','b','c','d','e')) };
std::unique_ptr<Eraser> v3{ static_cast<Eraser*>(new ErasedVector<double>(1.12, 2.34, 3.134, 4.51, 5.101)) };
items[std::type_index(typeid(int))] = std::move(v1);
items[std::type_index(typeid(char))] = std::move(v2);
items[std::type_index(typeid(double))] = std::move(v3);
}
template<typename... Ts>
std::tuple<Ts&...> get(size_t index)
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
template<typename... Ts, typename = std::enable_if_t<(std::is_const_v<Ts> && ...)>>
std::tuple<Ts&...> get(size_t index) const
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
private:
class Eraser
{
public:
virtual std::any operator[](size_t index) = 0;
virtual std::any operator[](size_t index) const = 0;
virtual ~Eraser() = default;
};
template <typename T>
class ErasedVector : public Eraser
{
public:
template <typename... Args>
ErasedVector(Args&&... args) :
data{ std::forward<Args>(args)... }
{
}
virtual std::any operator[](size_t index) override final
{
return std::reference_wrapper{ data[index] };
};
virtual std::any operator[](size_t index) const override final
{
return std::reference_wrapper{ data[index] };
}
private:
std::vector<T> data;
};
std::unordered_map<std::type_index, propagate_const<std::unique_ptr<Eraser>>> items;
};
It works properly on this example:
int main()
{
Container co;
auto [i0_0, c0_0, d0_0] = co.get<int, char, double>(0);
std::cout << i0_0 << ' ' << c0_0 << ' ' << d0_0 << '\n';
i0_0 = 3; // is a reference
d0_0 = 42; // is a reference
auto [i0_1, d0_1] = static_cast<const Container&>(co).get<const int, const double>(0); // works on const Container
std::cout << i0_1 << ' ' << d0_1; // original values modified
// i0_1 = 0xDEADBEEF; can be const too
}
And outputs:
1 a 1.12
3 42
Demo

Simply:
template<typename... Ts>
auto get_all(int index) {
return std::tuple<Ts&...>(get<Ts>(index)...);
}
Demo

You can use std::tie to take all of the "returns" from get<Ts>(index) can pack them into a tuple of references. That would look like
template<typename... Ts>
auto get_all(int index) {
return std::tie(get<Ts>(index)...);
}

Related

convert c multidimensional array to multidimensional c++ vector

i am trying to convert c multidimensional array to multidimensional c++ vector, i mean, to convert something like this int arr[2][3] = {{1,2,3}, {4,5,6}}; into the correspondent vector.
The array isn't necessarily 2D shaped, it could also be something like this:
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
{10,11,12},
}
};
initially i thought that something like this would have worked, but it didn't really turned out to be the case because it seems like if std::vector doesn't allows conversions from C arrays.
std::vector<std::any> V(arr);
Then i thought at something like function recursion, this is my attempt, that (i don't know why!) throws error: no matching function for call to 'length' .
#include <iostream>
#include <type_traits>
#include <vector>
#include <any>
// Get the lenght of a classic C array.
template <class T, unsigned S>
inline unsigned length(const T (&v)[S]) {
return S;
};
// Check wether the input is a classic C array or not.
template <class T>
bool is(const T& t) {
return std::is_array_v<T>;
};
// Turn the classic C input array to vector.
template <class T>
std::vector<std::any> toVector(const T& t) {
std::vector<std::any> V;
for (int k = 0; k < length(t); k++) {
if (is(t[k])) {
V.push_back(toVector(t[k]));
} else {
V.push_back(t[k]);
}
}
return V;
}
int main() {
int16 a[] = {1,2,3};
auto b = toVector(a);
}
What did i wrong in the second attempt? Alternatively, is there a simpler way to manage to do this?
Also, i think it would be better to convert all the numbers in the vector to a unique given data type, is this possible?
I am using c++11 and g++ as compiler –
Note that i do not know how many dimensions my array has.
The equivalent of a C multidimensional array is a flat C++ vector plus a mumtidimensional array view.
The naive equivalent is a vector of vectors (maybe of vectors), which actually corresponds to a C "jagged" array. The memory layout and performance characteristics will be very different.
There are many multi dimensional array-view implementstions on the web.
template<class A>
struct raw_ptr { using type=A*; };
template<class A, std::size_t N>
struct raw_ptr<A[N]>:raw_ptr<A>{};
template<class A>
using raw_ptr_t = typename raw_ptr<A>::type;
template<class T>
struct array_view;
template<class T, std::size_t D0, std::size_t D1>
struct array_view<T[D0][D1]> {
T(*data)[D1]=0;
constexpr array_view( T(&arr)[D0][D1] ):data(arr) {}
explicit array_view( raw_ptr_t<T> buff ):data(reinterpret_cast<T(*)[D1]>(buff)) {}
constexpr array_view<T[D1]> operator[](std::size_t i)const{
return data[i];
}
};
template<class T, std::size_t D0>
struct array_view<T[D0]> {
constexpr array_view( T(&arr)[D0] ):data(arr) {}
explicit constexpr array_view( T* buff ):data(buff) {}
T* data=0;
constexpr T& operator[](std::size_t i)const{
return data[i];
}
};
to convert a int[4][5][6] you'd do:
int array[4][5][6]={/*whatever*/};
std::vector<int> buff(&array[0][0][0], &array[3][4][5]);
array_view<int[4][5][6]> view{buff.data()};
now, view[a][b][c] is the same as array[a][b][c].
Live example.
template<class Array>
struct wrap_array_in_vector {
using raw_ptr = raw_ptr_t<Array>;
using value_type = std::remove_pointer_t<raw_ptr>;
std::vector<value_type> data;
array_view<Array> view;
wrap_array_in_vector(wrap_array_in_vector const& other):
data(other.data),
view(data.data())
{}
wrap_array_in_vector(wrap_array_in_vector && other):
data(std::move(other.data)),
view(data.data())
{
other.view.data = nullptr; // no longer valid
}
decltype(auto) operator[](std::size_t i)const {
return view[i];
}
wrap_array_in_vector( Array const& arr ):
data( reinterpret_cast<value_type const*>(&arr[0]), reinterpret_cast<value_type const*>(&arr[1]) ),
view(data.data())
{}
};
template<class Array>
wrap_array_in_vector(Array&)->wrap_array_in_vector<Array>;
template<class Array>
wrap_array_in_vector(Array const&)->wrap_array_in_vector<Array>;
this lets you do
wrap_array_in_vector wrapped = array;
and wrapped deduces all of its type information it needs.
This should be close to optimal for conversion to a vector. The part of the description that was misleading was that the sizes are not regular, as they are list-initialized and in this case with int, will be zeros.
#include <type_traits>
#include <vector>
#include <iostream>
template<typename T, std::size_t N>
auto to_vectors( T const (&c_array)[N] ) {
using element_base_type = std::decay_t<T>;
if constexpr( std::is_array_v<T> ) {
using child_t = std::remove_cv_t<std::remove_reference_t<decltype( to_vectors( c_array[0] ) )>>;
auto result = std::vector<child_t>( );
result.reserve( N );
for( auto const & element: c_array ) {
result.push_back( to_vectors( element ) );
}
return result;
} else {
return std::vector<T>( c_array, c_array + N );
}
}
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
}
};
auto v0 = to_vectors( arr );
template<typename Vec>
void display( Vec const & vec ) {
if constexpr( std::is_same_v<int, typename Vec::value_type> ) {
std::cout << "elements: {";
for( int e: vec ) {
std::cout << e << ',';
}
std::cout << "}\n";
} else {
std::cout << "element count: " << vec.size( ) << '\n';
for( auto const & child: vec ) {
display( child );
}
}
}
int main( ) {
display( v0 );
}
this will output
element count: 2
element count: 2
elements: {1,2,3,}
elements: {4,5,6,}
element count: 2
elements: {7,8,9,}
elements: {0,0,0,}

Store either constant or non constant reference within the same class template

The example below is degenerate because I would like to learn about the concept.
Let's say we would like to have a 1-element view of an array.
My question is how to make it work with both const and non-const objects.
I know why the second block in the code below does not compile but I don't know how to organize the code to serve both cases.
#include <cassert>
#include <array>
template <typename T>
class View {
private:
const std::size_t index_;
T &t_;
using value_type = typename T::value_type;
public:
View(T &t, std::size_t index) : t_{t}, index_{index} {}
const value_type &value() const { return t_[index_]; }
value_type &value() { return t_[index_]; }
};
int main() {
using Array = std::array<int, 2>;
// The block below works
{
Array array{0, 0};
View<Array> view(array, 0);
view.value() = 5;
assert(array[0] == 5);
}
// The block below gives a compilation error
{
const Array array{5, 5};
View<Array> view(array, 0);
assert(view.value() == 5);
}
}
The following works:
#include <cassert>
#include <array>
template <typename T>
class View {
private:
using value_type = typename T::value_type;
T &t_;
const std::size_t index_;
public:
View(T &t, std::size_t index) : t_{t}, index_{index} {}
const value_type &value() const { return t_[index_]; }
template<class Arr = T, class = typename std::enable_if<!std::is_const<Arr>::value>::type>
value_type &value() { return t_[index_]; }
};
int main() {
using Array = std::array<int, 2>;
// The block below works
{
Array array{0, 0};
View<Array> view(array, 0);
view.value() = 5;
assert(array[0] == 5);
}
// The block below gives a compilation error
{
const Array array{5, 5};
View<const Array> view(array, 0);
assert(view.value() == 5);
}
}
If you give the View a const Array you also have to specify a const Array as template argument.
But then returning a non-const reference with value() doesn't work anymore, so we disable this function with SFINAE, if the array type is const.
PS: You wouldn't have the last problem if your class named View were indeed what one would expect under a view, i.e. non-modifiying and not even having a method returning a non-const reference.

Compile time creation of class member stl container (const std::array) filled with elements

I tried to create a minimal example as it is possible with templates.
(MSVC15, c++14)
There are 2 questions may be connected with each other.
Question 1: Is it possible to create at compile time container class member (for example std::array) filled with elements?
_limited_int_ptr_container _intIndexes
{
// _startIntIndex
// create indexes???
std::make_shared<_limited_int_>(_startIntIndex), // 10060u _startStrIndex
//std::make_shared<_limited_int_>(_startIntIndex + 1),
//std::make_shared<_limited_int_>(_startIntIndex + 2),
//...
std::make_shared<_limited_int_>(_startIntIndex + _maxIntIndexes -1)
};
Question 2: Is it possible to combine several template classes with one general template parameter but only once?
template <class T, size_t Size>
using _container = const std::array<T, Size>;
template <class T>
using _ptr = std::shared_ptr<T>;
// is it possible to combine this 2 types and use _maxStrIndexes only once?
using _limited_str_ = IndexStr<_maxStrIndexes>;
using _limited_str_ptr = _ptr<_limited_str_>;
using _limited_str_ptr_container = _container<_limited_str_ptr, _maxStrIndexes>;
Full code:
#include <iostream>
#include <memory>
#include <array>
template<class T, size_t MaxIndex>
class BaseIndex
{
public:
virtual ~BaseIndex() = default;
explicit BaseIndex(const size_t index)
: _maxIndex(MaxIndex), _currentIndex(index) {}
virtual void DoSmth() = 0;
friend std::ostream & operator << (std::ostream & ss, BaseIndex & base_index)
{
ss << "Max: " << base_index._maxIndex << ". Current: " << base_index._currentIndex << std::endl;
return ss;
}
protected:
const size_t _maxIndex;
size_t _currentIndex{ 0u };
};
template<size_t MaxIndex>
class IndexStr : public BaseIndex<std::string, MaxIndex>
{
public:
explicit IndexStr(const size_t index) :BaseIndex(index) {}
void DoSmth() override { std::cout << IndexStr::_maxIndex; }
};
template<size_t MaxIndex>
class IndexInt :public BaseIndex<int, MaxIndex>
{
public:
explicit IndexInt(const size_t index) :BaseIndex(index) {}
void DoSmth() override { std::cout << IndexInt::_maxIndex; }
};
class Manager
{
private:
static const size_t _startStrIndex{ 11u };
static const size_t _startIntIndex{ 10060u };
static const size_t _maxStrIndexes{ 5 };
static const size_t _maxIntIndexes{ 120 };
public:
template <class T, size_t Size>
using _container = const std::array<T, Size>;
template <class T>
using _ptr = std::shared_ptr<T>;
// is it possible to combine this 2 types and use _maxStrIndexes only once?
using _limited_str_ = IndexStr<_maxStrIndexes>;
using _limited_str_ptr = _ptr<_limited_str_>;
using _limited_str_ptr_container = _container<_limited_str_ptr, _maxStrIndexes>;
// const std::array<std::shared_ptr<IndexStr<_maxStrIndexes>>, _maxStrIndexes> _strIndexes
_limited_str_ptr_container _strIndexes
{
// _startStrIndex
// create indexes ???
// std::integer_sequence ???
std::make_shared<_limited_str_>(_startStrIndex), // 11 = _startStrIndex
std::make_shared<_limited_str_>(12),
std::make_shared<_limited_str_>(13),
std::make_shared<_limited_str_>(14),
std::make_shared<_limited_str_>(_startStrIndex + _maxStrIndexes - 1)
};
// is it possible to combine this 2 types and use _maxIntIndexes only once?
using _limited_int_ = IndexInt<_maxIntIndexes>;
using _limited_int_ptr = _ptr<_limited_int_>;
using _limited_int_ptr_container = _container<_limited_int_ptr, _maxIntIndexes>;
_limited_int_ptr_container _intIndexes
{
// _startIntIndex
// create indexes???
std::make_shared<_limited_int_>(_startIntIndex), // 10060u _startStrIndex
//...
std::make_shared<_limited_int_>(_startIntIndex + _maxIntIndexes -1)
};
};
template <class T, size_t Size >
void Process(const std::array<std::shared_ptr<T>, Size> _array)
{
for (auto &&baseindex : _array)
std::cout << *baseindex;
}
int main()
{
Manager m;
Process(m._strIndexes);
//Process(m._intIndexes);
return 0;
}
Live demo
About the first question: yes, it is possible. It is even quite easy provided that you can use C++11 and variadic templates.
To generate a compile-time list of indices, you can use std::make_index_sequence<N>, which will return a std::index_sequence<0, 1, 2, 3, ..., N-1>. You can then pattern-match it with the function creating the compile-time array:
template <std::size_t... Ns>
constexpr auto fill_it_at_compile_time_impl(std::index_sequence<Ns...>) {
return std::array<unsigned, sizeof...(Ns)>{ Ns... };
}
template <std::size_t N>
constexpr auto fill_it_at_compile_time() {
return fill_it_at_compile_time_impl(std::make_index_sequence<N>());
}
If you want to add an offset to the index_sequence members, just do so in the array initialization:
constexpr auto offset = 10u;
template <std::size_t... Ns>
constexpr auto fill_it_at_compile_time_impl(std::index_sequence<Ns...>) {
return std::array<unsigned, sizeof...(Ns)>{ (offset+Ns)... };
}
About the second question:
As far as I understand it, yes, it's possible. First, create a helper struct to query the index of _limited_str:
template <typename T>
struct query_max_index;
template <std::size_t N>
struct query_max_index<IndexStr<N>> {
static const auto max_index = N;
};
Then, rather than refering to _maxStrIndexes directly, you can query it from indirectly from _limited_str_ptr:
using _limited_str_ = IndexStr<_maxStrIndexes>;
using _limited_str_ptr = _ptr<_limited_str_>;
using _limited_str_ptr_container = _container<_limited_str_ptr, query_max_index<std::decay_t<decltype(*std::declval<_limited_str_ptr>())>>::max_index>;

Make function generic with respect to pointers and references

Suppose I have this C++ function:
class C { ... };
void do(const vector<C>& cs) {
...
for (...) {
cs[i].do_whatever();
}
...
}
But C is expensive to copy so I might have something like this:
std::vector<C*> reorder_in_some_way(const std::vector<C>& cs) {
...
}
int main() {
std::vector<C> cs = ...;
std::vector<C*> reorderedCs = reorder_in_some_way(cs);
do(reorderedCs);
}
Obviously this won't work. I could get around it by giving up and just making do a template over any type like this:
template<typename T>
void do(const vector<T>& cs) {
But it really only works with C's and I'd like that to be encoded in the type system - and also it makes do() easier to understand if you don't have to go hunting around for places where it is used.
Is there any way to write do() so that it can generically take both vector<C> and vector<C*> (and for bonus points vector<reference_wrapper<C>>)?
Just write 2 template functions that applies a functor:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( i );
}
template<class T,typename Func>
void apply( const std::vector<T*> &v, Func f )
{
for( auto i : v ) f( *i );
}
then pass a lambda:
std::vector<C> vc;
std::vector<C*> vp;
auto call = []( const C &c ) { c.do_whatever(); };
apply( vc, call );
apply( vp, call );
(note you cannot call your function do - it is a keyword in C++)
live example
PS As you mentioned in comments your function apply is rather complex so you prefer to have only one copy of it, in this case create a helper:
template<class T>
const T &apply_helper( const T *t ) { return *t; }
template<class T>
typename std::enable_if<!std::is_pointer<T>::value, const T &>::type
apply_helper( const T &t ) { return t; }
then write your apply function only once:
template<class T,typename Func>
void apply( const std::vector<T> &v, Func f )
{
for( const auto &i : v ) f( apply_helper( i ) );
}
live example N2
You might keep your do function generic, but specialize a getter for T& and T* that both return a T&:
namespace detail{
template<class T>
T& get(T& _in){
return _in;
}
template<class T>
T& get(T* _in){
return *_in;
}
} // namespace detail
template<class T>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
Demo
Either way you are going to need to specialize between pointers and references. I think that this pushes it to the smallest scope.
If you want to constrain do_a_thing to only accept C or C*, we can create a small trait to do this:
template <class T>
struct is_c : std::false_type{};
template <>
struct is_c<C>: std::true_type{};
template <>
struct is_c<C*>: std::true_type{};
And then modify do_a_thing with std::enable_if:
template<class T, std::enable_if_t<is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
for (size_t i = 0; i < cs.size(); ++i) {
detail::get(cs[i]).do_whatever();
}
}
For bonus points, we'll write another specialization of do_a_thing that gives a nice compiler error for types that do not satisfy the constraint:
template<class T>
struct always_false : std::false_type{};
template<class T, std::enable_if_t<!is_c<T>::value, int> = 0>
void do_a_thing(const std::vector<T>& cs) {
static_assert(always_false<T>::value, "do_a_thing only works for C and C*");
}
Now the following will fail:
struct Q{};
std::vector<Q> qs;
do_a_thing(qs); // compiler error
Demo
Write a function template that gets a pair of iterators (not a vector).
Then pass it either normal vector<C>::iterators, or adapted vector<C*>::iterators, e.g. boost::transform_iterator instances.
Working example:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <boost/iterator/transform_iterator.hpp>
int& deref(int*& x) { return *x; }
template <class it>
void print(it from, it to)
{
std::copy(from, to, std::ostream_iterator<typename it::value_type>(std::cout, " "));
std::cout << "\n";
}
int main()
{
std::vector<int> a {4,3,7,1};
std::vector<int*> b {new int(2), new int(0), new int(11), new int(-3)};
// auto deref = [](int*& x) -> int& { return *x; };
// cannot use a lambda here because it's non-copyable
// and iterators must be copyable.
std::sort(std::begin(a), std::end(a));
std::sort(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
print(std::begin(a), std::end(a));
print(boost::make_transform_iterator(std::begin(b), &deref),
boost::make_transform_iterator(std::end(b), &deref));
}
I think a possible solution could be to create a modified vector class that is generic with respect to pointerness, and can be implicitly converted to from a vector<T> or a vector<T*>. Like this:
template<typename T>
class VectorWrapper {
public:
VectorWrapper(const vector<T>& v) : reference(&v) { }
VectorWrapper(const vector<T*>& v) : pointer(&v) { }
const T& at(int idx) const {
if (reference)
return (*reference)[idx];
return *(*pointer)[idx];
}
// etc. for size() and so on. You could probably have
// this class derive from std::vector and reimplement its
// methods to switch between `reference` and `pointer`.
private:
const vector<T>* reference = nullptr;
const vector<T*>* pointer = nullptr;
};
void do_thing(VectorWrapper<C> wrapper) {
wrapper.at(0).whatever();
}
Not tested, and I don't think I'll go this route to be honest but it's the best I could come up with.

C++ template class taking both const and non-const types

I would like to have a wrapper class that keeps and returns a pointer to some element of a wrapped container. It looks like:
template <typename T>
class VectorWrapper
{
public:
VectorWrapper(vector<T>& container) {
m_pointer = &container[0];
}
T* GetPointer() { return m_pointer; }
private:
T* m_pointer;
};
The problem is the input container can be a const type sometimes. In this case, is there an elegant way to avoid another implementation of VectorWrapper that rather returns const T*?
My approach (without luck) was the following:
template <typename T>
VectorWrapper<T> make_vector_wrapper(vector<T>& container) {
return VectorWrapper<T>(container);
}
template <typename T>
VectorWrapper<T> make_vector_wrapper(const vector<T>& container) {
// I'm stuck here. return VectorWrapper<const T>(container); doesn't work.
}
void Foo(const vector<int>& const_container) {
vector<int> mutable_container(10);
auto v1 = make_vector_wrapper(mutable_container);
*(v1.GetPointer()) = 1; // Ok
auto v2 = make_vector_wrapper(const_container);
int x = *(v2.GetPointer()); // Ok
*(v2.GetPointer()) = 1; // Would like compile error
}
You could template on the container and not the element, since it is the container that is const. Here is a quick mock-up (which does not work if the vector is relocated):
#include <iostream>
#include <vector>
template <typename T>
class VectorWrapper
{
using value_type = std::remove_reference_t<decltype(((T*)nullptr)->at(0))>;
public:
VectorWrapper(T& container) {
m_pointer = &container[0];
}
value_type* GetPointer() { return m_pointer; }
private:
value_type* m_pointer;
};
template <typename T>
VectorWrapper<T> make_vector_wrapper(T& container) {
return VectorWrapper<T>(container);
}
int main() {
std::vector<int> mutable_vector(10);
auto v1 = make_vector_wrapper(mutable_vector);
*(v1.GetPointer()) = 1; // Ok
const std::vector<int> const_vector(10);
auto v2 = make_vector_wrapper(const_vector);
// *(v2.GetPointer()) = 1; // error
}
Edit:
Here is a simpler solution (C++14 for auto return type), that also handles relocation of the underlying vectors data.
template <typename T>
class VectorWrapper
{
T& m_container;
public:
VectorWrapper(T& container) : m_container(container) {}
auto GetPointer() { return m_container.data(); }
};
Update:
It will also work for other container classes such as std::string and std::array
std::string mutable_string;
auto s1 = make_vector_wrapper(mutable_string);
// *(s1.GetPointer()) = 1; // error, sd::string::data returns char const *
std::array<int, 4> mutable_array;
auto a1 = make_vector_wrapper(mutable_array);
*(a1.GetPointer()) = 1; // Ok
const std::array<int, 4> const_array = {1,2,3,4};
auto a2 = make_vector_wrapper(const_array);
// *(a2.GetPointer()) = 1; // error