The problem seems simple enough yet I'm only able to solve it the "ugly" way. Here's a short code:
#include <array>
struct A {
A(int , int = 0) {}
A(std::array<const int, 2>) {}
//A(std::array<int, 2>) {}
};
int main(){
std::array<int, 2> a = {0};
const A x(a);
return 0;
}
As-is the compiler is trying to use the A(int, int = 0) constructor and, of course, fails the std::array to int conversion.
Commenting out the first constructor gives a clear compiler error that std::array<int, 2> cannot be automatically converted into it's const counterpart. And this is somewhat puzzling to me as I'd expect a non-const to const conversion to be "trivial".
The issue is resolved by introducing a third constructor (commented out in the code sample) but that looks like an overkill.
My questions are:
Why is the non-const to const conversion not done automatically here?
Can this be "fixed" without introducing the third, non-const version of the constructor?
Changing the constructor to accept gsl::span instead of std::array also helps, yet also feels like an overkill as well
I'm compiling on MSVC 2017 15.7.4 using the C++17 setting.
1) Why is the non-const to const conversion not done automatically here?
Because std::array<T, Dim> const and std::array<T const, Dim> are different types and, how say my clang++, "no known conversion from 'array<int, [...]>' to 'array<const int, [...]>'"
2) Can this be "fixed" without introducing the third, non-const version of the constructor?
What about a template constructor
template <typename T>
A (std::array<T, 2> const &) {}
where T can match both int and int const ?
If you want impose that T is only int or int const (and not, by example, long const) you can do it through SFINAE with something as
template <typename T>
A (std::array<T, 2>,
std::enable_if_t<std::is_same<T const, int const>{}> * = nullptr)
{ }
So you can have
std::array<int, 2> a = {{0}};
std::array<int const, 2> b = {{0}};
std::array<long const, 2> c = {{0}};
const A x(a); // compile
const A y(b); // compile
const A z(c); // compilation error
3) Changing the constructor to accept gsl::span instead of std::array also helps, yet also feels like an overkill as well
Sorry but I don't understand the third question (?) (and I don't know gls::span)
Related
In the following code, a class owns a dynamically-allocated array. It exposes a read-only access to this array via a public method that does an implicit conversion to const.
#include <array>
template <class T, std::size_t N>
class A {
const unsigned int size;
std::array<T, N> *s;
public:
A(const unsigned int _size, const std::array<T, N>& def) :
size(_size),
s(new std::array<T, N>[size])
{
for (unsigned int i = 0; i < size; i++)
s[i] = def;
}
~A() { delete[] s; }
std::array<T, N> const* const conf() const { return s; }
};
int main()
{
A a(10, std::array<int, 3>{0, 0, 0});
auto x = a.conf();
return 0;
}
Does the implicit const conversion in A::conf() give rise to an overhead (for example by invoking a copy-constructor of the elements of A::s)?
Does the implicit const conversion in A::conf() give rise to an overhead (for example by invoking a copy-constructor of the elements of A::s)?
No.
There is no "overhead", a pointer can be implicitly converted in its const-version. Generally, the conversion generates zero assembly instruction.
Additional Notes
Empirical Proof
A::conf() does not call any copy-constructor. From the generated assembly (gcc -O3):
A<int, 3ul>::conf() const:
movq %rdi, %rax
ret
As you can see, it just returns the pointer without producing any further instructions.
Note: I disabled the "inlining optimization" here, otherwise the entire function would be optimized producing zero overhead.
Small Tips
Maybe a little bit off-topic, but it is good practice to avoid managing raw memory in modern C++ (whenever possible).
What about using std::vector?
In the signature std::array<T, N> const* const conf() const the second const is always ignored by the compiler. You can omit making it more readable: std::array<T, N> const* conf() const.
conf() returns a pointer (std::array<T, N> const*). Thus auto x = a.conf(); deduces the type of x to be a const pointer std::array<T, N> const*. The address/pointer is copied by value (typically copy 4byte/8byte address depending on your system). No c'tor is called.
I'm trying to write a class based around mathematical vectors:
template <unsigned N> class Vector{
public:
Vector() = default;
Vector(std::initializer_list<double> li) { *this = li;}
Vector& operator=(std::initializer_list<double>);
private:
std::array<double, N> x = {}
}
template <unsigned N> inline Vector<N>& Vector<N>::operator=(std::initializer_list<double> li){
if(N != li.size()) throw std::length_error("Attempt to initialise Vector with an initializer_list of different size.");
std::copy(li.begin(), li.end(), x.begin());
return *this;
}
I want to be able to write code like this;
Vector<3> a = {1,2,3};
a = {3,5,1};
It would be natural for a user to expect to write code like that, right? However I want compile-time errors to occur if I use the wrong-sized initializer list, much like std::array does.
std::array<double, 3> a = {2,4,2,4} //compile time error
Vector<3> a = {3,5,1,5} //run-time error as of right now
My first idea was to use std::array as the constructor/operator parameter so implicit conversions would occur and then the constructor would hijack, from std::array, the compile time errors. Except of course I could only write code like this:
Vector<3> a({2,3,2}); //fine
Vector<3> b = {2,4,2}; //error, requires two user-defined conversions (list -> array<double,3> -> Vector<3>)
I thought maybe to use a Variadic member template:
template <typename... Args> Vector(Args... li): x({li...}){
static_assert(sizeof...(li) == N);
}
It has to be typename... rather than double... because nontype parameters must be integral types. But then I run in to a narrowing conversion error
Vector<2> a = {3,2} //error: narrowing conversion of 'li#0' from 'int' to 'double' inside { } [-Wnarrowing]|
//error: narrowing conversion of 'li#1' from 'int' to 'double' inside { } [-Wnarrowing]|
Presumably for violating [8.5.4]/7
A narrowing conversion is an implicit conversion
— from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or
The parameters from expanding li... aren't constant expressions and hence produce the narrowing conversion error. As far as I'm aware it wouldn't even be possible to make function parameters as constant expressions (nor would it make much sense?). So I'm not sure how to carry on down that route. Obviously Vector<2> a = {2.,3.} works fine but this puts a burden on the user to remember only to supply floating-point literals.
You can make your constructor a variadic template so that any condition can be used:
#include <array>
#include <cstddef>
template<typename T, std::size_t N>
class vector
{
public:
vector(T&& value)
: data{static_cast<T>(value)}
{}
template<typename U>
vector(const vector<U,N>& v)
{
std::copy(begin(v.data), end(v.data),
begin(data));
}
template<typename U>
vector(const vector<U,N>& v)
{
std::copy(begin(v.data), end(v.data),
begin(data));
}
template<typename... U,
typename = typename std::enable_if<sizeof...(U)-1>::type>
vector(U&&... values)
: data{static_cast<T>(values)...}
{
static_assert(sizeof...(values) == N, "wrong size");
}
std::array<T,N> data;
};
int main()
{
vector<int, 3> v = {1,2,3};
vector<double, 4> vv = {5,4,3,2};
vv = {1,2,3,4};
//vector<float, 3> vf = {1,2,3,4}; // fails to compile
vector<float,3> vf = v;
}
Live example on coliru.
It gets you a custom error message, easily adaptable/extensible condition for failure, and gets rid of the "narrowing conversion" problem by effectively forwarding the initialization to the std::array initializer like you wanted to do in the first place. Oh, and you get assignment for free.
As #M.M mentions, this solution ruins copy construction, unfortunately. You can solve it by adding an enable_if on the variadic arguments "array" size as shown above. Of course you need to be careful to not ruin assignment/copy-construction and single-element vectors, which is remedied by adding two extra constructors for these special cases.
This code seems to work, for both constructor and assignment-operator:
#include <array>
template<size_t N>
struct Vector
{
Vector() = default;
template<typename...Args>
Vector(double d, Args... args)
{
static_assert(sizeof...(args) == N-1, "wrong args count");
size_t idx = 0;
auto vhelp = [&](double d) { x[idx++] = d; };
vhelp(d);
double tmp[] { (vhelp(args), 1.0)... };
}
Vector &operator=(Vector const &other) = default;
private:
std::array<double, N> x = {};
};
int main()
{
Vector<5> v = { 1,2,3,4,5 };
v = { 3,4,5,6,7 };
Vector<1> w = { 1,2 }; // error
}
The assignment operator works because the constructor is implicit, so v = bla attempts to convert bla to match the only definition of operator=.
I made the first argument double d instead of just using all variadic args, to avoid the issue where all-variadic-args constructor catches calls that were supposed to be copy-construction.
The line involving double tmp[] uses what I call the variadic template comma operator hack. This hack has many uses, but here it lets us avoid the narrowing-conversion issue that double tmp[] { args... }; has.
(TBH though, incorporating rubvenvb's idea and using double tmp[] { static_cast<double>(args)... }; would be simpler)
I have a struct that manages 'views' into an array of variable type. The purpose of this is to provide a unified state vector for ODE simulation while at the same time working on individual segments of this vector from several other classes in an organized fashion. If this triggers a design pattern in your mind, please let me know.
My issue is that the first implementation ContainerHolderFirst, using Cont::pointer does not compile for const arrays.
My next attempt with std::conditional, mixing in Cont::const_pointer still doesn't work.
Only the third attempt with std::conditional and modification of Cont::value_type compiles (and seems to work in my larger project).
My questions are the following:
It would be nice if ContainerHolderFirst would work. I'd wish that const-ness of the type would be propagated to the pointer. Why isn't it?
I understand even less why ContainerHolderSecond doesn't work. The explanation in https://stackoverflow.com/a/1647394/1707931 rather suggests that this is the way to go, no? It does actually work. My bad.
Are there issues with the third approach that I haven't uncovered yet? Is there a simpler way?
Full C++11 code follows:
Update1: Fixing ContainerHolderSecond. It does compile with correct initialization. Also added ContainerHolderBarry suggested by Barry using decltype and declval.
This leaves the question whether any of the approaches are preferred? Will they lead to performance differences? They should all compile to the same object, no?
#include <iostream>
#include <array>
template <typename Cont>
class ContainerHolderFirst {
Cont& data_;
const static size_t offset_ = 1;
typename Cont::pointer data_view;
public:
ContainerHolderFirst(Cont& data) : data_(data), data_view(&data[offset_]) {}
};
template <typename Cont>
class ContainerHolderSecond {
using Pointer = typename std::conditional<std::is_const<Cont>::value,
typename Cont::const_pointer,
typename Cont::pointer>::type;
Cont& data_;
const static size_t offset_ = 1;
Pointer data_view;
public:
ContainerHolderSecond(Cont& data) : data_(data), data_view(&data[offset_]) {}
};
template <typename Cont>
class ContainerHolderBarry {
using Pointer = decltype(&std::declval<Cont&>()[0]);
Cont& data_;
const static size_t offset_ = 1;
Pointer data_view;
public:
ContainerHolderBarry(Cont& data) : data_(data), data_view(&data[offset_]) {}
};
int main() {
using namespace std;
array<int, 2> my_array;
ContainerHolderFirst<array<int, 2>> holder(my_array); // works
const array<int, 2> const_array{5,7};
// ContainerHolderFirst<const array<int, 2>> const_holder(const_array);
/* error: invalid conversion from 'const value_type* {aka const int*}' to 'std::array<int, 2ull>::pointer {aka int*}' [-fpermissive] */
ContainerHolderSecond<array<int,2>> second_holder(my_array); // works!
ContainerHolderSecond<const array<int,2>> const_holder(const_array); //updated; works as well; awkward
ContainerHolderThird<array<int,2>> third_holder(my_array); // still works
ContainerHolderThird<const array<int,2>> third_const_holder(const_array); //finally compiles as well
ContainerHolderBarry<array<int,2>> barry_holder(my_array);
ContainerHolderBarry<const array<int,2>> barry_const_holder(const_array);
}
You're making this unnecessarily difficult on yourself. If you want the type of &cont[offset], just ask for the type of that expression. Use std::declval along with decltype:
template <typename Cont>
class ContainerHolder {
using Pointer = decltype(&std::declval<Cont&>()[0]);
...
};
The only problem with ContainerHolderSecond is that you're using it incorrectly:
ContainerHolderSecond<array<int,2>> const_holder(const_array);
// ^-- insert "const" here
As for ContainerHolderFirst, the reason that array<T, N>::pointer is the same type as (array<T, N> const)::pointer is that there is no automatic way to determine where the const qualification should be added to the nested type, and there is no language facility to describe this (that is, we don't have const-qualified typedefs or type aliases).
Is the following a valid C++ code, and why not?
std::array<std::string, 42> a1;
std::array<int, a1.size()> a2;
It doesn't compile in GCC 4.8 (in C++11 mode). There is a simple but inelegant workaround:
std::array<std::string, 42> a1;
std::array<int, sizeof(a1)/sizeof(a1[0])> a2;
So clearly the compiler can figure out the number of elements in std::array. Why std::array::size() is not a constexpr static function?
EDIT:
I have found another workaround:
std::array<std::string, 42> a1;
std::array<int, std::tuple_size<decltype(a1)>::value> a2;
array<T>::size() is constexpr, but you can't use it in this way because a1 isn't a constexpr value. Additionally, it can't be constexpr because string isn't a literal type.
However, you can work around this if you want, by deducing the size_t template parameter. Example:
#include <string>
#include <array>
#include <iostream>
using namespace std;
template<typename>
struct array_size;
template<typename T, size_t N>
struct array_size<array<T,N> > {
static size_t const size = N;
};
array<string, 42> a1;
array<string, array_size<decltype(a1)>::size> a2;
int main() {
cout << a2.size() << endl;
}
std::array::size is actually required to be constexpr per § 23.3.2.1 of the C++11 standard:
23.3.2.4 array::size [array.size]
template <class T, size_t N> constexpr size_type array<T,N>::size() noexcept;
Returns: N
I'm guessing this just slipped past whoever implemented it in GCC.
After testing, this works:
std::array<int, 42> a1;
std::array<int, a1.size()> a2;
This may actually have something to do with std::string not being a valid constexpr type to make compile-time instances of, whereas int is.
You can use the same template-inference method as has always been used for C++98 array bound detection.
template<size_t N, typename T>
constant_integer<N> array_size( const std::array<T, N>& );
Demo: http://ideone.com/R4k1vG
Make a nice macro wrapper and enjoy!
Many variations are also possible, such as:
http://ideone.com/Hn46ei
It is not static but it is a constexpr http://www.cplusplus.com/reference/array/array/size/
EDIT: this may not be a bug, take a look at this Error using a constexpr as a template parameter within the same class
//THIS IS JUST A FRAGMENT OF A static_numeric_limits.h for the purpose of this example
#include <limits.h>
template<class T>
struct static_numeric_limits;
template<>
struct static_numeric_limits<signed char>
{/*min was outside of range for enum*/
static const signed char min = SCHAR_MIN,
max = SCHAR_MAX;
};
/*This "surplus" template is here for the reason that char is threated differently from signed char */
template<>
struct static_numeric_limits<char>
{/*min was outside of range for enum*/
static const char min = SCHAR_MIN,
max = SCHAR_MAX;
};
template<>
struct static_numeric_limits<unsigned char>
{
static const unsigned char min = 0x0,
max = UCHAR_MAX;
};
///REAL PROBLEM STARTS FROM HERE
template<class IntType,IntType low_range = static_numeric_limits<IntType>::min>
struct Int
{
Int():value_(IntType())
{}
Int(const IntType& pattern)
{
value_ = (pattern);
}
constexpr inline IntType getValue()const
{
return value_;
}
private:
IntType value_;
};
template<class IntType,class IntType_1>
auto operator+
(Int<IntType>& lhs, Int<IntType_1>& rhs)
-> Int<decltype(lhs.getValue() + rhs.getValue())>//HERE IS THE PROBLEM
{
return lhs.getValue() + rhs.getValue();
}
Error (from VS2010)
error C2027: use of undefined type 'static_numeric_limits<T>'
Error (from gcc 4.6)
error: 'decltype ((lhs->getValue() + rhs->getValue()))' is not a valid type for a template constant parameter
Why doesn't this work as I thought it would?
The error here is what type decltype is deducing from your expression; unfortunately the error messages aren't clear about it, and it's actually a bit of a tricky problem.
Consider the type of the expression 0 + 0. It's an int, yes, but more importantly it's an rvalue (informally, it's a temporary). This means that decltype(0 + 0) is not int, but int&&. Now consider that your code isn't any different, in this regard: you still have an rvalue.
The problem is that template non-type parameters cannot be rvalue references, so you cannot have Int<int&&>, because of the second parameter's type . What you can do, though is this:
#include <type_traits>
// ...
template <class IntType, class IntType_1>
auto operator+(const Int<IntType>& lhs, // be const-correct!
const Int<IntType_1>& rhs)
-> Int<typename std::remove_reference<
decltype(lhs.getValue() + rhs.getValue())>::type>
{
return lhs.getValue() + rhs.getValue();
}
This takes the reference off int&&, giving you the bare int type. Hopefully gcc's error message makes a bit more sense: it's trying to tell you that you can't use int&& for your non-type parameter.
Another problem, though probably a non-issue, is that integer arithmetic undergoes what's called the usual arithmetic conversions. So the result of adding the values of two Int<char>'s is actually going to be an int, so your return type should be Int<int> (and is, with the fixed code).
The problem, then, is that you haven't defined static_numeric_limits<int>. But like I said, I suspect this is a non-issue and you do actually have it defined, just not displayed in your question.