Ambigous call of constructor with gcc 8.2.1 - c++

This program defines a simple container for an array of std::unique_ptr<unsigned int>.
#include <memory>
#include <array>
#include <type_traits>
template <unsigned int dim>
class container {
template <std::size_t... I>
container(std::array<unsigned int, dim> Ls, std::index_sequence<I...>) : container(std::get<I>(Ls)...) { }
public:
const std::array<std::unique_ptr<unsigned int>, dim> data;
template <typename... UInts, class = std::enable_if_t<(sizeof...(UInts) == dim) && (std::is_same_v<UInts, unsigned int> && ...)>>
container(UInts... Ls) : data{ std::make_unique<unsigned int>(Ls)...} { }
template <typename Indices = std::make_index_sequence<dim>>
container(std::array<unsigned int, dim> Ls) : container(Ls, Indices{}) { }
};
int main()
{
unsigned int x = 1;
unsigned int y = 2;
container<2> a({x,y});
return 0;
}
The compilation with gcc 8.2.1 fails however, with the following error.
$ g++ -Wall -pedantic -std=c++17 -o test test.cpp
test.cpp: In function ‘int main()’:
test.cpp:25:23: error: call of overloaded ‘container(<brace-enclosed initializer list>)’ is ambiguous
container<2> a({x,y});
^
test.cpp:17:3: note: candidate: ‘container<dim>::container(std::array<unsigned int, dim>) [with Indices = std::integer_sequence<long unsigned int, 0, 1>; unsigned int dim = 2]’
container(std::array<unsigned int, dim> Ls) : container(Ls, Indices{}) { }
^~~~~~~~~
test.cpp:6:7: note: candidate: ‘container<2>::container(const container<2>&)’ <deleted>
class container {
^~~~~~~~~
test.cpp:6:7: note: candidate: ‘container<2>::container(container<2>&&)’ <deleted>
make: *** [Makefile:3: test] Error 1
Indeed, the copy constructor is deleted because of the presence of the array of std::unique_ptr (but still participates to the overload resolution).
Strangely, on gcc 7.3.0 the above code compiles with no errors or warnings.
Also, defining a simplified container, having a copyable array of unsigned int, as in the following program, no error occurs with both gcc 8.2.1 and gcc 7.3.0
#include <array>
#include <type_traits>
template <unsigned int dim>
class container_simple {
template <std::size_t... I>
container_simple(std::array<unsigned int, dim> Ls, std::index_sequence<I...>) : container_simple(std::get<I>(Ls)...) { }
public:
const std::array<unsigned int, dim> data;
template <typename... UInts, class = std::enable_if_t<(sizeof...(UInts) == dim) && (std::is_same_v<UInts, unsigned int> && ...)>>
container_simple(UInts... Ls) : data{ Ls...} { }
template <typename Indices = std::make_index_sequence<dim>>
container_simple(std::array<unsigned int, dim> Ls) : container_simple(Ls, Indices{}) { }
};
int main()
{
unsigned int x = 1;
unsigned int y = 2;
container_simple<2> a({x,y});
return 0;
}
Is it a compiler bug? If not, where is the ambiguity?

Related

Variadic constructor fails to initialise an std::array

Here is a snippet from my code:
template <typename Type, unsigned int NumberOfRows, unsigned int NumberOfColumns>
class HexMatrix
{
private:
std::array<Type, NumberOfRows*NumberOfColumns> values;
// ...
public:
template <typename... OtherType>
HexMatrix(OtherType...);
// ...
};
template <typename Type, unsigned int NumberOfRows, unsigned int NumberOfColumns>
template <typename... OtherType>
HexMatrix<Type, NumberOfRows, NumberOfColumns>::HexMatrix(OtherType... args) : values({ args... })
{
static_assert(NumberOfRows != 0u);
static_assert(NumberOfColumns != 0u);
}
Until recently, I was able to use this code as is, and initialise the std::array with an std::vector using the constructor above. Then my PC got formatted. Now this doesn't compile anymore, even though I was using and am still using -std=c++2a. Here is the compilation report:
In file included from foo.cpp:3:
LinearAlgebra.hpp: In instantiation of ‘HexMatrix< <template-parameter-1-1>, <anonymous>, <anonymous> >::HexMatrix(OtherType ...) [with OtherType = {std::vector<long double, std::allocator<long double> >}; Type = long double; unsigned int NumberOfRows = 6; unsigned int NumberOfColumns = 6]’:
LinearAlgebra.hpp:230:9: required from ‘static HexMatrix<Type, NumberOfRows, NumberOfColumns> HexMatrix< <template-parameter-1-1>, <anonymous>, <anonymous> >::Make(OtherType ...) [with OtherType = {std::vector<long double, std::allocator<long double> >}; Type = long double; unsigned int NumberOfRows = 6; unsigned int NumberOfColumns = 6]’
foo.cpp:93:80: required from here
LinearAlgebra.hpp:299:98: error: no matching function for call to ‘std::array<long double, 36>::array(<brace-enclosed initializer list>)’
299 | HexMatrix<Type, NumberOfRows, NumberOfColumns>::HexMatrix(OtherType... args) : values({ args... })
| ^
In file included from /usr/include/c++/9/tuple:39,
from /usr/include/c++/9/bits/hashtable_policy.h:34,
from /usr/include/c++/9/bits/hashtable.h:35,
from /usr/include/c++/9/unordered_map:46,
from Using.hpp:5,
from LinearAlgebra.hpp:4,
from foo.cpp:3:
/usr/include/c++/9/array:94:12: note: candidate: ‘std::array<long double, 36>::array()’
94 | struct array
| ^~~~~
/usr/include/c++/9/array:94:12: note: candidate expects 0 arguments, 1 provided
/usr/include/c++/9/array:94:12: note: candidate: ‘constexpr std::array<long double, 36>::array(const std::array<long double, 36>&)’
/usr/include/c++/9/array:94:12: note: no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const std::array<long double, 36>&’
/usr/include/c++/9/array:94:12: note: candidate: ‘constexpr std::array<long double, 36>::array(std::array<long double, 36>&&)’
/usr/include/c++/9/array:94:12: note: no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::array<long double, 36>&&’
make: *** [Makefile:16: foo.o] Error 1
What's wrong? I tried to remove the parenthesis, and/or add another pair of brackets around args... but that didn't change anything. Please help?
Reproductible example:
int main(void)
{
const std::vector<long double> foo = { 1.L, 2.L, -6.L, 0.L };
HexMatrix<long double, 2u, 2u> givenMatrix(foo);
return 0;
}
If you want to pass a vector in the constructor, add a new constructor to HexMatrix:
#include <vector>
#include <array>
#include <algorithm>
template <typename Type, unsigned int NumberOfRows, unsigned int NumberOfColumns>
class HexMatrix
{
private:
std::array<Type, NumberOfRows*NumberOfColumns> values;
// ...
public:
template <typename... OtherType>
HexMatrix(OtherType...);
HexMatrix(const std::vector<Type>&);
// ...
};
template <typename Type, unsigned int NumberOfRows, unsigned int NumberOfColumns>
template <typename... OtherType>
HexMatrix<Type, NumberOfRows, NumberOfColumns>::HexMatrix(OtherType... args) : values({ args... })
{
static_assert(NumberOfRows != 0u);
static_assert(NumberOfColumns != 0u);
}
template <typename Type, unsigned int NumberOfRows, unsigned int NumberOfColumns>
HexMatrix<Type, NumberOfRows, NumberOfColumns>::HexMatrix(const std::vector<Type>& vec)
{
static_assert(NumberOfRows != 0u);
static_assert(NumberOfColumns != 0u);
std::copy_n(vec.begin(), NumberOfRows*NumberOfColumns, values.begin());
}
int main(void)
{
const std::vector<long double> foo = { 1.L, 2.L, -6.L, 0.L };
HexMatrix<long double, 2u, 2u> givenMatrix(foo);
return 0;
}

Why can't the .size() of an array passed to a function as a constant reference be used as a template parameter?

I can't seem to figure out why the following code doesn't work:
#include <array>
template <long unsigned int s> void a() {}
template <long unsigned int s> void b(const std::array<int, s>& arr) {
a<arr.size()>(); // error: no matching function for call to 'a'
}
int main() {
const std::array<int, 2> arr {{0, 0}};
a<arr.size()>(); // Works
b<arr.size()>(arr);
return 0;
}
GCC fails with the following:
test.cpp: In instantiation of ‘void b(const std::array<int, s>&) [with long unsigned int s = 2]’:
test.cpp:13:22: required from here
test.cpp:6:18: error: no matching function for call to ‘a<(& arr)->std::array<int, 2>::size()>()’
6 | a<arr.size()>(); // Doesn't
| ~~~~~~~~~~~~~^~
test.cpp:3:37: note: candidate: ‘template<long unsigned int s> void a()’
3 | template <long unsigned int s> void a() {}
| ^
test.cpp:3:37: note: template argument deduction/substitution failed:
test.cpp:6:18: error: ‘arr’ is not a constant expression
6 | a<arr.size()>(); // Doesn't
| ~~~~~~~~~~~~~^~
test.cpp:6:15: note: in template argument for type ‘long unsigned int’
6 | a<arr.size()>(); // Doesn't
| ~~~~~~~~^~
I assume the ‘arr’ is not a constant expression part is most relevant, but I don't understand why the same line works in main() (is it a constant expression there?), and why passing arr as a const copy (rather than a reference) also resolves the issue.
PS: I know that I can just use a<s>();, but I'm just trying to figure out what this error means.
Maybe in C++2X, it will work with consteval functitons.
For non-reference argument it works https://godbolt.org/z/Wx9va54z5.
I think it is fine to pass std::array of built-ins by value in general anyway.
(This works in GCC and clang.)
#include <array>
template <long unsigned int s> void a() {}
template <long unsigned int s> void b(std::array<int, s> arr) {
a<arr.size()>(); // ok
}
int main() {
const std::array<int, 2> arr = {};
a<arr.size()>(); // Works
b<arr.size()>(arr);
return 0;
}
To answer the question in your comment #acumandr, yes, a static and constexpr size() member function in std::array would work(!), even for const& argument. https://godbolt.org/z/anP1e9qEr
CORRECTION: This is correct only for GCC, in clang doesn't work https://godbolt.org/z/65d5G9Yfo , Thanks #IlCapitano
#include <array>
template<class T, std::size_t D>
struct MyArray{
static constexpr std::size_t size(){return D;}
};
template <long unsigned int s> void a() {}
template <long unsigned int s> void b(MyArray<int, s> const& arr) {
a<arr.size()>(); // ok
}
int main() {
const MyArray<int, 2> arr = {};
a<arr.size()>(); // Works
b<arr.size()>(arr);
return 0;
}
which makes even more puzzling why std::array doesn't have a static (and constexpr) size member.
2.5) CORRECTION: This is correct only for GCC, in clang doesn't work https://godbolt.org/z/65d5G9Yfo , Thanks #IlCapitano
In clang, a static funciton works but not passing the instance, which kind of defeats the purpose:
https://godbolt.org/z/En13aEWPz
#include <array>
template<class T, std::size_t D>
struct MyArray{
static constexpr std::size_t size(){return D;}
};
template <long unsigned int s> void a() {}
template <long unsigned int s> void b(MyArray<int, s> const& arr) {
a<std::decay_t<decltype(arr)>::size()>(); // error: no matching function for call to 'a'
}
int main() {
const MyArray<int, 2> arr = {};
a<arr.size()>(); // Works
b<arr.size()>(arr);
return 0;
}
using std::tuple_size<decltype(...)> (or s itself) is a good workaround https://godbolt.org/z/K9xxKa1Px
#include <array>
template <long unsigned int s> void a() {}
template <long unsigned int s> void b(std::array<int, s> arr) {
a<std::tuple_size<decltype(arr)>::value /*or just s*/>(); // ok
}
int main() {
const std::array<int, 2> arr = {};
a<arr.size()>(); // Works
b<arr.size()>(arr);
return 0;
}

conversion error from make_integer_sequence to integer_sequence

The following program does not compile:
#include <utility>
#include <iostream>
#define N 4
template <unsigned int I>
unsigned int g() { return I; }
template <unsigned int... I>
unsigned int f(std::integer_sequence<unsigned int, I...> = std::make_integer_sequence<unsigned int, N>{})
{
return (g<I>() + ...);
}
int main()
{
std::cout << f() << std::endl;
return 0;
}
Test it live on Coliru.
With gcc the error is
main.cpp: In function 'unsigned int f(std::integer_sequence<unsigned
int, I ...>) [with unsigned int ...I = {}]':
main.cpp:17:18: error: could not convert
'std::make_integer_sequence<unsigned int, 4>{}' from
'integer_sequence<[...],'nontype_argument_pack' not supported by
dump_expr>' to
'integer_sequence<[...],'nontype_argument_pack' not supported by
dump_expr>'
A similar conversion error is reported with clang++:
error: no viable conversion from 'std::make_integer_sequence<unsigned
int, 4>' (aka '__make_integer_seq<integer_sequence, unsigned int,
4U>') to 'std::integer_sequence'
Strangely enough, however, if I remove the default parameter, and pass the same expression to f, the program compiles and gives the corrected output:
#include <utility>
#include <iostream>
#define N 4
template <unsigned int I>
unsigned int g() { return I; }
template <unsigned int... I>
unsigned int f(std::integer_sequence<unsigned int, I...>)
{
return (g<I>() + ...);
}
int main()
{
std::cout << f(std::make_integer_sequence<unsigned int, N>{}) << std::endl;
return 0;
}
See it live on Coliru.
What's the problem/difference with the first code?
I admit, I do not understand the error message. The reason the second version compiles but not the first is that template parameters cannot be deduced from default arguments but from function parameters. Consider this simpler example:
#include <utility>
#include <iostream>
template <unsigned int>
struct foo {};
template <unsigned int x>
foo<x> make_foo(){ return {};}
template <unsigned int x>
unsigned int f(foo<x> = make_foo<4>())
{
return 42;
}
int main()
{
std::cout << f() << std::endl;
return 0;
}
Here the error is a little more descriptive:
<source>: In function 'int main()':
<source>:18:18: error: no matching function for call to 'f()'
18 | std::cout << f() << std::endl;
| ^
<source>:11:14: note: candidate: 'template<unsigned int x> unsigned int f(foo<x>)'
11 | unsigned int f(foo<x> = make_foo<4>())
| ^
<source>:11:14: note: template argument deduction/substitution failed:
<source>:18:18: note: couldn't deduce template parameter 'x'
18 | std::cout << f() << std::endl;
| ^
The main purpose of make_integer_sequence<unsigned int,N> is to make the transition from a single N to the pack in std::integer_sequence<unsigned int, I...> as you do it in your second example.
With a level of indirection you avoid that the caller must pass the parameter:
// ...
template <unsigned int... I>
unsigned int f(std::integer_sequence<unsigned int, I...>)
{
return (g<I>() + ...);
}
template <unsigned int X = N>
unsigned int f_wrap()
{
return f(std::make_integer_sequence<unsigned int,N>{});
}
int main()
{
std::cout << f_wrap() << std::endl;
return 0;
}
Live Demo

Problem with std::index_sequence_for as default argument?

The following C++20 program:
#include <utility>
#include <cstddef>
template<typename... Args>
class C {
template<size_t... I>
static void call(
std::index_sequence<I...> = std::index_sequence_for<Args...>{}
) {}
};
int main() {
C<long int>::call();
}
fails to compile with error message:
test.cc: In static member function ‘static void C<Args>::call(std::index_sequence<I ...>) [with long unsigned int ...I = {}; Args = {long int}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int>]’:
test.cc:11:20: error: could not convert ‘std::index_sequence_for<long int>{}’ from ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’ to ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’
11 | C<long int>::call();
| ^
| |
| integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>
test.cc:11:20: note: when instantiating default argument for call to ‘static void C<Args>::call(std::index_sequence<I ...>) [with long unsigned int ...I = {}; Args = {long int}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int>]’
test.cc: In function ‘int main()’:
test.cc:11:20: error: could not convert ‘std::index_sequence_for<long int>{}’ from ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’ to ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’
Any ideas?
Update:
My current best workaround is to factor out default argument into two functions like:
template<typename... Args>
class C {
static void call() {
_call(std::index_sequence_for<Args...>{});
}
template<size_t... I>
static void _call(std::index_sequence<I...>) {}
};
This seems to work around compiler bug (if that's what it is).
Update 2:
The below program fails for the same reason the original one does:
template<typename T> void f(T x = 42) {}
int main() { f(); }
so it's a feature not a bug.
In general template argument deduction + default function arguments cause a lot of trouble. To simply fix it you can go with this:
#include <utility>
#include <cstddef>
template<typename... Args>
class C {
public:
static void call() {
call_impl(std::index_sequence_for<Args...>{});
}
private:
template<size_t... I>
static void call_impl(std::index_sequence<I...> ) {
}
};
int main() {
C<long int>::call();
}
In C++20 you can also write a templated lambda to do exactly that without creating a new function:
//...
static void call() {
return [&]<size_t... Is>(std::index_sequence<Is...>) {
/* your code goes here... */
}( std::index_sequence_for<Args...>{} );
}

Non-type template parameters, constructor and deduction guide

I want to have a struct template, that is defined by particular values of its components passed to it during construction, such that different values would create different C++ data types, and thought non-type template parameters may be useful for that.
Something like this (just a simple example to show the issue, the real struct will be more complex):
enum ElementType
{
TYPE1,
TYPE2
};
template<ElementType elementType, int size>
struct DataType
{
DataType(ElementType et = elementType, int s = size):
elementType_(et),
size_(s)
{
}
ElementType elementType_;
int size_;
};
int main()
{
auto d1 = DataType(ElementType::TYPE1, 1);
}
I try to build this with g++-8 -std=c++17 and it gives me the following error:
./main.cpp:23:42: error: class template argument deduction failed:
auto d1 = DataType(ElementType::TYPE1, 1);
^
../main.cpp:23:42: error: no matching function for call to ‘DataType(ElementType, int)’
../main.cpp:12:2: note: candidate: ‘template<ElementType elementType, int size> DataType(ElementType, int)-> DataType<elementType, size>’
DataType(ElementType et = elementType, int s = size):
^~~~~~~~
../main.cpp:12:2: note: template argument deduction/substitution failed:
../main.cpp:23:42: note: couldn't deduce template parameter ‘elementType’
auto d1 = DataType(ElementType::TYPE1, 1);
^
../main.cpp:23:42: error: expression list treated as compound expression in functional cast [-fpermissive]
../main.cpp:23:42: warning: left operand of comma operator has no effect [-Wunused-value]
Note that I cannot use type template arguments, since the two types of the arguments are fixed (ElementType and int), but DataType(ElementType::TYPE1, 1) must be of different type than DataType(ElementType::TYPE1, 2) and DataType(ElementType::TYPE1, 1) must be different than DataType(ElementType::TYPE2, 1).
You can define your template like this:
template<ElementType elementType, int size>
struct DataType
{
const ElementType elementType_ = elementType;
const int size_ = size;
};
And create an instance of it like this:
auto d1 = DataType<ElementType::TYPE1, 1>();
Demo
To make use of deduction the value you pass to the constructor needs to be a constant expression. Unfortunately values passed as parameter loose their constexpr properties. To prevent this behavior you can pass the values wrapped in types e.g. using std::integral_constant.
Exemplary usage:
#include <type_traits>
enum ElementType
{
TYPE1,
TYPE2
};
template<ElementType elementType, int size>
struct DataType
{
DataType(std::integral_constant<ElementType, elementType>, std::integral_constant<int, size> ic):
elementType_(elementType),
size_(ic)
{
}
ElementType elementType_;
int size_;
};
int main()
{
auto d1 = DataType(std::integral_constant<ElementType, TYPE1>{}, std::integral_constant<int, 1>{});
}
[live demo]
To make it more convenient to use you could wrap around integral const with constexpr suffix operators:
#include <type_traits>
enum ElementType
{
TYPE1,
TYPE2
};
template <class... Ts>
constexpr int ival(Ts... Vs) {
char vals[sizeof...(Vs)] = {Vs...};
int result = 0;
for (int i = 0; i < sizeof...(Vs); i++) {
result *= 10;
result += vals[i] - '0';
}
return result;
}
template <class T, class... Ts>
constexpr ElementType etval(T V, Ts... Vs) {
if (V == '1')
return TYPE1;
if (V == '2')
return TYPE2;
}
template <char... Vs>
std::integral_constant<int, ival(Vs...)> operator""_i() {
return {};
}
template <char... Vs>
std::integral_constant<ElementType, etval(Vs...)> operator""_et() {
return {};
}
template<ElementType elementType, int size>
struct DataType
{
DataType(std::integral_constant<ElementType, elementType>, std::integral_constant<int, size> ic):
elementType_(elementType),
size_(ic)
{
}
ElementType elementType_;
int size_;
};
int main()
{
auto d1 = DataType(1_et, 1_i);
}
[live demo]