I am trying construct a constexpr union which wraps 3 types. The union can contain a raw uint8_t array or fixed length arrays of PODTypeA or PODTypeB. The lengths of these arrays are known at compile time and given by constexpr values.
The POD structures are very simple:
struct PODTypeA {
int a;
int b;
};
or
struct PODTypeB {
uint32_t a;
uint32_t b;
};
The embedded environment does not have the standard template library available. As such, I cannot use the constexpr std::array<T,N> which would make things a lot easier.
Shown below is a constexpr Array(const Args&... args) array implementation, which should function as a constexpr data member to hold array fields.
template <typename T, std::size_t Size>
struct Array {
T data[Size];
template <typename ...Args>
constexpr Array(const Args&... args)
: data{ args... }
{}
};
I don't fully understand how the parameter pack expansion works (effectively memcpy(ing) the array via the compiler to the specified union member). If someone could explain that it would be a plus. I found the above Array template parameter pack expander in the checked answer to the following stack overlflow question (where there is also a live example).
I put this in a coliru project but I am having lots of compile issues.
I have 3 constructors for the union, each one of these takes a fixed length array parameter as follows:
constexpr static auto gSizeBytes = 2048;
constexpr static int gArraySizeA = 2;
constexpr static int gArraySizeB = 3;
union UnionStruct {
explicit constexpr UnionStruct(
const uint8_t(&rParam)[gSizeBytes] = {})
: byteArray(rParam)
{}
explicit constexpr UnionStruct(
const PODTypeA(&rParam)[gArraySizeA])
: arrayA(rParam)
{}
explicit constexpr UnionStruct(
const PODTypeB(&rParam)[gArraySizeB])
: arrayB(rParam)
{}
Array<uint8_t, gSizeBytes> byteArray;
Array<PODTypeA, gArraySizeA> arrayA;
Array<PODTypeB, gArraySizeB> arrayB;
};
And here is how I expect to use it:
int main()
{
PODTypeA[UnionStruct::gArraySizeA] arrayTypeA = {{1,2}, {3,4}};
// construct union from fixed PODTypeA array with 2 elements
UnionStruct foo (arrayTypeA);
}
The compiler issues the following errors:
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In instantiation of 'constexpr Array<T, Size>::Array(const Args& ...) [with Args = {unsigned char [2048]}; T = unsigned char; long unsigned int Size = 2048]':
main.cpp:34:27: required from here
main.cpp:12:25: error: invalid conversion from 'const unsigned char*' to 'unsigned char' [-fpermissive]
: data{ args... }
^
main.cpp: In instantiation of 'constexpr Array<T, Size>::Array(const Args& ...) [with Args = {PODTypeA [2]}; T = PODTypeA; long unsigned int Size = 2]':
main.cpp:39:24: required from here
main.cpp:12:25: error: invalid conversion from 'const PODTypeA*' to 'int' [-fpermissive]
main.cpp: In instantiation of 'constexpr Array<T, Size>::Array(const Args& ...) [with Args = {PODTypeB [3]}; T = PODTypeB; long unsigned int Size = 3]':
main.cpp:44:24: required from here
main.cpp:12:25: error: invalid conversion from 'const PODTypeB*' to 'uint32_t' {aka 'unsigned int'} [-fpermissive]
main.cpp: In function 'int main()':
main.cpp:54:14: error: expected identifier before numeric constant
PODTypeA[2] arrayTypeA = {{1,2}, {3,4}};
^
main.cpp:54:14: error: expected ']' before numeric constant
PODTypeA[2] arrayTypeA = {{1,2}, {3,4}};
^
]
main.cpp:54:13: error: structured binding declaration cannot have type 'PODTypeA'
PODTypeA[2] arrayTypeA = {{1,2}, {3,4}};
^
main.cpp:54:13: note: type must be cv-qualified 'auto' or reference to cv-qualified 'auto'
main.cpp:54:13: error: empty structured binding declaration
main.cpp:54:17: error: expected initializer before 'arrayTypeA'
PODTypeA[2] arrayTypeA = {{1,2}, {3,4}};
^~~~~~~~~~
main.cpp:56:22: error: 'arrayTypeA' was not declared in this scope
UnionStruct foo (arrayTypeA);
^~~~~~~~~~
main.cpp:56:22: note: suggested alternative: 'gArraySizeA'
UnionStruct foo (arrayTypeA);
^~~~~~~~~~
gArraySizeA
main.cpp:54:13: warning: unused structured binding declaration [-Wunused-variable]
PODTypeA[2] arrayTypeA = {{1,2}, {3,4}};
^
Related
I need to make a template specialization which distinguish between a template class with (only) value parameters like the following:
template<auto ... __vz>
struct values{};
and a template class with (only) reference parameters like the following:
template<auto& ... __lvz>
struct lvalues{};
But with following specialization I was not successful:
template<typename>
struct is_values{
static constexpr bool value = false;
};
template<template<auto ...>class __m, auto ... __vz>
struct is_values<__m<__vz ...> >{
static constexpr bool value = true;
};
template<typename>
struct is_lvalues{
static constexpr bool value = false;
};
template<template<auto& ...>class __m, auto& ... __vz>
struct is_lvalues<__m<__vz ...> >{
static constexpr bool value = true;
};
When they are tested like the following:
const int a = 1;
const int b = 2;
const int c = 3;
int main()
{
std::cout << is_values<lvalues<a,b,c> >::value <<std::endl; // wrong result
std::cout << is_lvalues<values<1,2,3> >::value <<std::endl; // causes error
}
One of them gives a wrong result and one gives the following error:
test.cpp: In instantiation of ‘struct is_lvalues<values<1, 2, 3> >’:
test.cpp:51:41: required from here
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
32 | struct is_lvalues<__m<__vz ...> >{
| ^
test.cpp:32:34: error: could not convert ‘1’ from ‘int’ to ‘int&’
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:32:34: error: could not convert ‘2’ from ‘int’ to ‘int&’
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:32:34: error: could not convert ‘3’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
33 | static constexpr bool value = true;
| ^~~~~
test.cpp:33:24: error: could not convert ‘1’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:33:24: error: could not convert ‘2’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:33:24: error: could not convert ‘3’ from ‘int’ to ‘int&’
test.cpp: In function ‘int main()’:
test.cpp:51:43: error: ‘value’ is not a member of ‘is_lvalues<values<1, 2, 3> >’
51 | std::cout << is_lvalues<values<1,2,3> >::value <<std::endl; // causes error
| ^~~~~
make: *** [src/subdir.mk:20: src/test.o] Error 1
"make all" terminated with exit code 2. Build might be incomplete.
12:14:37 Build Failed. 14 errors, 0 warnings. (took 677ms)
So far I was only successful with the following specializations:
template<auto ... __vz>
struct is_values<values<__vz ...> >{
static constexpr bool value = true;
};
template<auto& ... __vz>
struct is_lvalues<lvalues<__vz ...> >{
static constexpr bool value = true;
};
But these are not desired because they only detect the types resulted from values and lvalues not any other similar template with different name.
So what is the right specialization?
If you're solving this for types, you can use
std::is_lvalue_reference to do this for a single type. Since you seem to be using C++17, you can use fold expressions to apply this to a parameter pack:
template <typename... Ts>
using are_types_lvalues = std::integral_constant<bool, (std::is_lvalue_reference_v<Ts> && ...)>;
template <typename... Ts>
using are_types_values = std::integral_constant<bool, !(std::is_reference_v<Ts> || ...)>;
If you can test the types, you can just use decltype to get a non-type parameter's type, and not worry about forcing an argument to bind to auto or auto&...
template<typename>
struct are_values : std::false_type {};
template<template<auto ...>class __m, auto ... __vz>
struct are_values<__m<__vz ...>> : std::integral_constant<bool, !(std::is_reference_v<decltype(__vz)> || ...)> {};
template<typename>
struct are_lvalues : std::false_type {};
template<template<auto ...>class __m, auto ... __vz>
struct are_lvalues<__m<__vz ...>> : std::integral_constant<bool, (std::is_lvalue_reference_v<decltype(__vz)> && ...)> {};
usage:
template<auto... __vz>
struct values{};
template<auto& ... __lvz>
struct lvalues{};
const int a = 1;
const int b = 2;
const int c = 3;
static_assert(are_values<values<1,2,3>>::value, "");
static_assert(!are_values<lvalues<a,b,c>>::value, "");
static_assert(!are_lvalues<values<1,2,3>>::value, "");
static_assert(are_lvalues<lvalues<a,b,c>>::value, "");
demo:
https://godbolt.org/z/KTh9Wj
The following main.cpp illustrates the problem:
#include <type_traits>
template <class T, std::size_t N>
struct Array
{
T data_[N];
};
template <const std::size_t* EltArray, std::size_t EltCount>
struct Foo
{
};
int main()
{
// SIDE NOTE if arr is not declared static: the address of 'arr' is not a valid template argument
// because it does not have static storage duration
static constexpr std::size_t arr[3] = {1, 2, 3};
Foo<arr, 3> foo;// WORKING
static constexpr Array<std::size_t, 3> arr2 = {1, 2, 3};
static constexpr const std::size_t* arr2_ptr = arr2.data_;
Foo<arr2_ptr, 3> foo2;// ERROR:
// 'arr2_ptr' is not a valid template argument of type 'const size_t*'
// {aka 'const long long unsigned int*'} because
// 'arr2.Array<long long unsigned int, 3>::data_' is not a variable
static constexpr const std::size_t* test = std::integral_constant<const std::size_t*, arr2_ptr>{};// ERROR:
// 'arr2_ptr' is not a valid template argument of type 'const long long unsigned int*' because
// 'arr2.Array<long long unsigned int, 3>::data_' is not a variable
return 0;
}
I don't understand why arr2.data_ is not reusable just like arr. Can someone explain ?
I'm using gcc: mingw-w64\x86_64-8.1.0-posix-sjlj-rt_v6-rev0
g++.exe -Wall -std=c++2a -fconcepts -O2
I want to share the answer i just found in open-std and a compliant solution.
We all know that we cannot pass any variable as non type.
Did you know that we can pass a const reference to anything we want ?!
So the solution is:
#include <array>
// When passing std::array<std::size_t, EltCount> (by value), i get the error:
// 'struct std::array<long long unsigned int, EltCount>' is not a valid type for a template non-type parameter
template <std::size_t EltCount, const std::array<std::size_t, EltCount>& Elts>
struct Foo {};
static constexpr std::array<std::size_t, 3> arr = {1, 2, 3};
int main()
{
Foo<3, arr> foo;// WORKING
return 0;
}
And the answer to the initial question is:
quote of the N4296
14.3.2 Template non-type arguments [temp.arg.nontype]
For a non-type template-parameter of reference or pointer type, the
value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
(1.1) — a subobject (1.8),
Moral of the story: we can do what we want with references, not with pointers.
The following code does not compile with GCC 5.2 (C++14). It does compile with clang 3.6 (C++14). (original code can be found here)
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <utility>
template <typename T>
class aggregate_wrapper;
template <typename T, std::size_t n>
class aggregate_wrapper<T[n]> {
public:
using array = T[n];
template <typename... Ts, typename = decltype(array{std::declval<Ts>()...})>
aggregate_wrapper(Ts&&... xs)
: arr_{std::forward<Ts>(xs)...} {
// nop
}
aggregate_wrapper(const array& arr) {
std::copy(arr, arr + n, arr_);
}
aggregate_wrapper(array&& arr) {
std::move(arr, arr + n, arr_);
}
operator T* () {
return arr_;
}
operator const T* () const {
return arr_;
}
constexpr std::size_t size() const {
return n;
}
private:
array arr_;
};
int main() {
aggregate_wrapper<int[3]> arr;
static_assert(arr.size() == 3, "");
}
The error message produced is
main.cpp: In function 'int main()':
main.cpp:44:3: error: non-constant condition for static assertion
static_assert(arr.size() == 3, "");
^
main.cpp:44:25: error: call to non-constexpr function 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]'
static_assert(arr.size() == 3, "");
^
main.cpp:34:25: note: 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]' is not usable as a constexpr function because:
constexpr std::size_t size() const {
^
main.cpp:34:25: error: enclosing class of constexpr non-static member function 'constexpr std::size_t aggregate_wrapper<T [n]>::size() const [with T = int; long unsigned int n = 3ul; std::size_t = long unsigned int]' is not a literal type
main.cpp:10:7: note: 'aggregate_wrapper<int [3]>' is not literal because:
class aggregate_wrapper<T[n]> {
^
main.cpp:10:7: note: 'aggregate_wrapper<int [3]>' is not an aggregate, does not have a trivial default constructor, and has no constexpr constructor that is not a copy or move constructor
Any ideas? Should the code compile according to the standard?
Or you could just make your existing variadic constructor serve as your constexpr constructor to perform the default construction:
template <typename... Ts, typename = decltype(array{std::declval<Ts>()...})>
constexpr // <---- ADD THIS
aggregate_wrapper(Ts&&... xs)
: arr_{std::forward<Ts>(xs)...} {
// nop
}
In order for g++ to get it compiled you will need to add a default constructor:
aggregate_wrapper() = default;
please see it in action at: http://coliru.stacked-crooked.com/a/df1ac057960bebc7
I have the feeling that clang under the hood added it, but I am not 100% sure ...
GCC is wrong. Its diagnostic, in part, says:
main.cpp:34:25: note: '<...>' is not usable as a constexpr function because:
main.cpp:34:25: error: enclosing class of constexpr non-static member function '<...>' is not a literal type
... but there is no such rule. See [dcl.constexpr]/3 for the list of constraints that apply here.
You can work around the bogus GCC diagnostic by adding a dummy constexpr constructor (it's fine for that constructor to be private and/or deleted if you don't want any of your real constructors to be constexpr) or by making size be static.
I have the following code:
#include <memory>
int main()
{
int* a = new int(2);
std::unique_ptr<decltype(*a)> p(a);
}
which leads to these error message:
In file included from a.cpp:1:
In file included from /usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/memory:81:
/usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/unique_ptr.h:138:14: error: '__test' declared as a pointer to a reference of type 'int &'
static _Tp* __test(...);
^
/usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/unique_ptr.h:146:35: note: in instantiation of member class 'std::unique_ptr<int &,
std::default_delete<int &> >::_Pointer' requested here
typedef std::tuple<typename _Pointer::type, _Dp> __tuple_type;
^
a.cpp:7:35: note: in instantiation of template class 'std::unique_ptr<int &, std::default_delete<int &> >' requested here
std::unique_ptr<decltype(*a)> p(a);
^
In file included from a.cpp:1:
In file included from /usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/memory:81:
/usr/bin/../lib64/gcc/x86_64-unknown-linux-gnu/4.9.2/../../../../include/c++/4.9.2/bits/unique_ptr.h:227:33: error: 'type name' declared as a pointer to a reference of type 'int &'
is_convertible<_Up*, _Tp*>, is_same<_Dp, default_delete<_Tp>>>>
^
a.cpp:7:35: note: in instantiation of template class 'std::unique_ptr<int &, std::default_delete<int &> >' requested here
std::unique_ptr<decltype(*a)> p(a);
^
2 errors generated.
I understand the reason is that the unique_ptr template expects type int, but decltype(*a) gives int&. In the case that int is a very long and complicated type, how can I make this code work with decltype?
Use std::decay_t. This is the conversion that is applied when you pass an argument to a function by value.
You can use a typedef inside a templated class and then use template specialisation, like this
template<typename T> struct unref {
typedef T raw;
};
template<typename T> struct unref<T&> {
typedef T raw;
};
int main() {
int* a = new int(2);
std::unique_ptr<unref<decltype(*a)>::raw> p(a);
}
I have this smart and cool example:
#include <iostream>
#include "boost/multi_array.hpp"
#include "boost/array.hpp"
#include "boost/cstdlib.hpp"
template <typename Array>
void print(std::ostream& os, const Array& A)
{
typename Array::const_iterator i;
os << "[";
for (i = A.begin(); i != A.end(); ++i) {
print(os, *i);
if (boost::next(i) != A.end())
os << ',';
}
os << "]";
}
void print(std::ostream& os, const double& x)
{
os << x;
}
int main()
{
typedef boost::multi_array<double, 2> array;
double values[] = {
0, 1, 2,
3, 4, 5
};
const int values_size=6;
array A(boost::extents[2][3]);
A.assign(values,values+values_size);
print(std::cout, A);
return boost::exit_success;
}
But if I try to compile it: g++ -I/usr/include/boost/ b.cpp I get this error:
b.cpp: In function ‘void print(std::ostream&, const Array&) [with Array = double]’:
b.cpp:12: instantiated from ‘void print(std::ostream&, const Array&) [with Array = boost::detail::multi_array::const_sub_array<double, 1u, const double*>]’
b.cpp:12: instantiated from ‘void print(std::ostream&, const Array&) [with Array = main()::array]’
b.cpp:32: instantiated from here
b.cpp:9: error: ‘double’ is not a class, struct, or union type
b.cpp:11: error: request for member ‘begin’ in ‘A’, which is of non-class type ‘const double’
b.cpp:9: error: ‘double’ is not a class, struct, or union type
b.cpp:9: error: request for member ‘end’ in ‘A’, which is of non-class type ‘const double’
b.cpp:9: error: ‘double’ is not a class, struct, or union type
b.cpp:9: error: ‘double’ is not a class, struct, or union type
b.cpp:9: error: ‘double’ is not a class, struct, or union type
b.cpp:13: error: request for member ‘end’ in ‘A’, which is of non-class type ‘const double’
b.cpp:9: error: ‘double’ is not a class, struct, or union type
shell returned 1
What was wrong? It seems to not understand the difference between the first and the second print function. Maybe some compiler options I missed?
EDIT:
If i use template<> as Craig H answer i resolve the problem in a isolated example.cpp. But if i put the 2 functions in a separate .h file in my project, the error appears again!
In this example you created a template function as your first print function then declare another function that implements a specific version of that template without template specializing. I think if you change the following line
void print(std::ostream& os, const double& x)
to
template<> void print<double>(std::ostream& os, const double& x)
your problem should go away.