I'm trying to sum the elements of a static array in compile-time via templates:
#include <type_traits>
template<size_t idx, int* arr>
struct static_accumulate :
std::integral_constant<size_t, arr[idx] + static_accumulate<idx - 1, arr>::value>
{ };
template<int* arr>
struct static_accumulate<0, arr> : std::integral_constant<size_t, arr[0]>
{ };
constexpr int arr[9] = {1, 2, 3,
4, 5, 6,
7, 8, 9};
int main()
{
std::cout<<static_accumulate<8, arr>::value;
}
but I got this compile-error:
error: could not convert template argument ‘arr’ to ‘int*’
compiler - gcc 4.7.
How I can avoid it?
Change your template arguments to int const * arr.
Clang's error message is actually more helpful here than gcc's:
sum.cc:19:37: error: non-type template argument of type
'int const[9]' cannot be converted to
a value of type 'int *'
Related
I have been devloping a class template for a stack-allocated N-dimensional array (note that I have not yet implemented support for >1-D arrays):
template <typename T, std::size_t... DIMS> class ndarray {
static constexpr std::size_t NDIM{sizeof...(DIMS)};
static constexpr std::array<T, NDIM> SHAPE{DIMS...};
static constexpr std::size_t SIZE{(1 * ... * DIMS)};
std::array<T, SIZE> _data;
public:
constexpr ndarray() : _data{} {}
constexpr ndarray(T element) : _data{element} {}
constexpr ndarray(const T (&elements)[SIZE])
: _data(std::to_array(elements)) {}
constexpr ndarray(const std::array<T, SIZE> &elements) : _data(elements) {}
constexpr std::size_t size() { return SIZE; }
constexpr std::array<T, NDIM> shape() { return SHAPE; }
constexpr ndarray &operator=(const T (&elements)[SIZE]) {
_data = std::to_array(elements);
}
};
template <typename T, std::size_t N>
ndarray(const T (&elements)[N]) -> ndarray<T, N>;
The above code allows for every method of initialization and assignment I am looking for except one:
// 0-D arrays
void zero() {
int z{1};
int x[1];
ndarray a(1);
ndarray b{1};
ndarray c = 1;
ndarray<int> d;
d = 1;
ndarray e(z);
ndarray f{z};
ndarray g = z;
ndarray<int> h;
h = z;
}
// 1-D arrays
void one() {
int y[]{1, 2};
std::array z{1, 2};
ndarray a({1, 2});
ndarray b{{1, 2}};
ndarray c = {1, 2};
ndarray<int, 2> d;
d = {1, 2};
ndarray e(y);
ndarray f{y};
ndarray g = y;
ndarray<int, 2> h;
h = y;
ndarray i(z);
ndarray j{z};
ndarray k = z;
ndarray<int, 2> l;
l = z;
}
error C2641: cannot deduce template arguments for 'ndarray'
error C2780: 'ndarray<T,N> ndarray(const T (&)[N])': expects 1 arguments - 2 provided
note: see declaration of 'ndarray'
error C2780: 'ndarray<T,DIMS...> ndarray(const std::array<T,ndarray<T,DIMS...>::SIZE> &)': expects 1 arguments - 2 provided
note: see declaration of 'ndarray'
error C2780: 'ndarray<T,DIMS...> ndarray(const T (&)[ndarray<T,DIMS...>::SIZE])': expects 1 arguments - 2 provided
note: see declaration of 'ndarray'
error C2780: 'ndarray<T,DIMS...> ndarray(T)': expects 1 arguments - 2 provided
note: see declaration of 'ndarray'
error C2780: 'ndarray<T,DIMS...> ndarray(void)': expects 0 arguments - 2 provided
note: see declaration of 'ndarray'
error C2780: 'ndarray<T,DIMS...> ndarray(ndarray<T,DIMS...>)': expects 1 arguments - 2 provided
note: see declaration of 'ndarray'
The copy-list-initialization of c in one is the cause of the compiler error. ndarray c = {{1, 2}} works, but this worries me because it is easily confused with an array of shape [1, N]. It also means that ndarray foo = 1 is equivalent to ndarray foo = {1}, which I strongly dislike.
Unfortunately, I cannot use either std::initializer_list or parameter pack constructor, as they would lead to some ambiguous cases (would ndarray foo{1} have shape [] or [1]?). The only option I have thought of is to make the constructors explicit to prevent copy-list-initialization entirely, although doing this would limit my class's interoperatibility with its contained type to some extent. Is there another way around this?
Edit: I just realized that ndarray foo({1}) is also ambiguous as it stands. Very frustrating.
Fixing ndarray c = initilization
You can initialize the std::array with double braces:
reference: How to construct std::array object with initializer list?
// 1-D arrays
void one() {
int y[]{1, 2};
std::array z{1, 2};
ndarray a({1, 2});
ndarray b{{1, 2}};
ndarray c = {{1, 2}}; // double braces
}
Fixing your error C2641: cannot deduce template arguments for 'ndarray'
By adding move constructor and reference constructor I'm able to compile your code:
constexpr ndarray(const T& element) : _data{element} {}
constexpr ndarray(const T&& element) : _data{element} {}
Fixing ambiguous ndarray foo({1})
if you want to make ndarray foo({1, 2}) create an array of 2 elements you must declare a constructor which takes in parameter a C array as follows:
constexpr ndarray(const T elements[]) {
_data = reinterpret_cast<std::array<int, SIZE>&>(elements);
}
I'm looking to make a loss function that can take 2 tensors of any dimensions as parameters, but the dimensions of tensor 1 (t1) and tensor (t2) must match. Below are the templates that I tried to use to can pass the tensors into the function. I was thinking that T would be a type and N would model the number of indexes possible without explicitly writing a type for infinitely possible tensor dimensions.
loss.h
#include <iostream>
namespace Loss {
template<class T, std::size_t N>
void loss(Eigen::Tensor<T, N, 0>& predicted, Eigen::Tensor<T, N, 0>& actual) {
std::cout << "Loss::loss() not implemented" << std::endl;
};
};
main.cpp
#include "loss.h"
int main() {
Eigen::Tensor<double, 3> t1(2, 3, 4);
Eigen::Tensor<double, 3> t2(2, 3, 4);
t1.setZero();
t2.setZero();
Loss::loss(t1, t2);
return 0;
}
The type error that I get before compiling from my editor:
no instance of function template "Loss::loss" matches the argument list -- argument types are: (Eigen::Tensor<double, 3, 0, Eigen::DenseIndex>, Eigen::Tensor<double, 3, 0, Eigen::DenseIndex>
And this is the message I get once I compile (unsuccessfully):
note: candidate template ignored: substitution failure [with T = double]: deduced non-type template argument does not have the same type as the corresponding template parameter ('int' vs 'std::size_t' (aka 'unsigned long'))
void loss(Eigen::Tensor<T, N, 0>& predicted, Eigen::Tensor<T, N, 0>& actual) {
^
1 error generated.
The error message is pointing out the type of the non-type template parameter is size_t, but in the declaration of t1 and t2 the value of that parameter is 3, which has type int. This mismatch makes the template argument deduction fail.
You can fix this by changing the type of the non-type template parameter to int
template<class T, int N>
void loss( // ...
or just let it be deduced
template<class T, auto N>
void loss( // ...
number literals are signed integers, and you’ve specified the number type of your template as size_t Which is unsigned. So the types don’t match. Try Eigen::Tensor<double, 3u> … in your main program to use unsigned literals.
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 program does not compile:
template <unsigned int dim, unsigned int N, bool P, bool C, class... ParametersType>
void test(ParametersType&&... par)
{
}
int main()
{
test<2, 3, true, false>(2, 1, {8, 8});
}
See it live on Coliru.
The error message
g++ -std=c++17 -O1 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:8:41: error: too many arguments to function 'void test(ParametersType&& ...)
[with unsigned int dim = 2; unsigned int N = 3; bool P = true; bool C = false; ParametersType = {}]'
8 | test<2, 3, true, false>(2, 1, {8, 8});
| ^
main.cpp:2:6: note: declared here
2 | void test(ParametersType&&... par)
| ^~~~
indicates that the parameter pack ParametersType... is deduced to an empty one, while I would expect it to be deduced according to the types of the arguments passed to test.
The problem is in the {8, 8} parameter passed to test.
Explicitly passing a std::array to the function solves the problem:
#include <array>
template <unsigned int dim, unsigned int N, bool P, bool C, class... ParametersType>
void test(ParametersType&&... par)
{
}
int main()
{
test<2, 3, true, false>(2, 1, std::array<int, 2>{8, 8});
}
See it live on Coliru.
Why does the compiler apparently incorrectly deduces the pack in the first example?
If the compiler is not able to deduce {8, 8} to an std::array, I would expect an "impossible to deduce" error. Why instead does the compiler deduce the pack to an empty one?
Template errors are hard to get right. It's just a quality of implementation. Clang for instances gives
main.cpp:2:6: note: candidate template ignored: substitution failure
[with dim = 2, N = 3, P = true, C = false]: deduced incomplete pack <int, int, (no value)>
for template parameter 'ParametersType'
which is easier to understand. And yes, unless using auto, {stuff} has no type.
From cppreference:
A braced-init-list is not an expression and therefore has no type,
e.g. decltype({1,2}) is ill-formed. Having no type implies that
template type deduction cannot deduce a type that matches a
braced-init-list, so given the declaration template void
f(T); the expression f({1,2,3}) is ill-formed.
You can also use auto in this context to fix your issue:
template <unsigned int dim, unsigned int N, bool P, bool C, class... ParametersType>
void test(ParametersType&&... par)
{
}
int main()
{
auto x = { 8, 8 };
test<2, 3, true, false>(2, 1, x);
}
In the C++ Standard there is the following deduction guide for the template class std::valarray<T>:
template<class T, size_t cnt> valarray(const T(&)[cnt], size_t) -> valarray<T>;
However among the constructors of the class there is only the following appropriate constructor (or am I mistaken?)
valarray(const T&, size_t);
However if to run the following demonstrative program with a similar deduction guide
#include <iostream>
#include <utility>
template <typename T>
struct A
{
A( const T &, size_t ) { std::cout << "A<T>()\n"; }
};
template<class T, size_t cnt>
A(const T(&)[cnt], size_t) -> A<T>;
int main()
{
int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
A obj( a, std::size( a ) );
}
the gcc compiler issues error
rog.cc:17:12: error: invalid conversion from 'int*' to 'int' [-fpermissive]
17 | A obj( a, std::size( a ) );
| ^
| |
| int*
prog.cc:7:8: note: initializing argument 1 of 'A<T>::A(const T&, size_t) [with T = int; size_t = long unsigned int]'
7 | A( const T &, size_t ) { std::cout << "A<T>()\n"; }
| ^~~~~~~~~
So a question arises whether it is a defect of the C++ Standard, or a bug of the compiler or I missed something.
Using the example given by https://en.cppreference.com/w/cpp/numeric/valarray/deduction_guides, we can look at the compiler output without optimizations to see which constructor is called:
int a[] = {1, 2, 3};
std::valarray va(a, 3);
https://godbolt.org/z/rtgeoi
main:
[...]
call std::valarray<int>::valarray(int const*, unsigned long)
[...]
C++ arrays decay to pointers to their first element very easily. However, without the deduction guide, the type deduced from the implicit guides would be std::valarray<int[3]> (the guide generated from valarray(const T&, size_t) wins because it does not need the array-to-pointer conversion). This can be demonstrated in your example, if we have both the A(const T&, std::size_t); and A(const T*, std::size_t); constructors:
template <typename T>
struct A
{
A(const T&, std::size_t);
A(const T*, std::size_t);
};
int main()
{
int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
A obj(a, 10);
}
// Result:
call A<int [10]>::A(int const (&) [10], unsigned long)
https://godbolt.org/z/nlFlVT
Adding the deduction guide correctly deduces the intended int instead of int[10]: https://godbolt.org/z/efj557
However among the constructors of the class there is only the following appropriate constructor (or am I mistaken?)
There's also this one:
valarray( const T* vals, std::size_t count );
Which does match, when the array you pass in decays to a pointer, allowing the cppreference example to compile.