Specializing template with integer_sequence. GCC vs MSVC - c++

So, I came across a piece of code that behaves differently in GCC and MSVC:
#include <utility>
typedef int IType;
template<typename> struct A;
template<int... Ns>
struct A<std::integer_sequence<IType, Ns...>> {
using type = bool;
};
using B = typename A<std::make_integer_sequence<IType, 3>>::type;
int main() {
B b;
}
This happily compiles on both compilers. However, if you define IType as typedef long IType; MSVC still works whereas GCC says:
source>:12:61: error: invalid use of incomplete type 'struct A<std::integer_sequence<long int, 0, 1, 2> >'
12 | using B = typename A<std::make_integer_sequence<IType, 3>>::type;
| ^~~~
<source>:5:27: note: declaration of 'struct A<std::integer_sequence<long int, 0, 1, 2> >'
5 | template<typename> struct A;
| ^
<source>: In function 'int main()':
<source>:15:3: error: 'B' was not declared in this scope
15 | B b;
| ^
Compiler returned: 1
So, obviously when IType is long GCC fails to use the 2nd more specialized definition of A and thus fails. I'm really struggling to understand why int and long are treated differently by GCC here.
I used GCC 10.1 and MSVC 19.24 in Compiler Explorer to play with it.
https://godbolt.org/z/7L3xap

std::integer_sequence is defined as
template< class T, T... Ints >
class integer_sequence;
That is, the type of the values is T.
So when changing IType to long, also the type of Ns... should be changed to long...:
typedef int IType;
template<typename> struct A;
template<IType... Ns> // <--- HERE
struct A<std::integer_sequence<IType, Ns...>> {
using type = bool;
};
Otherwise you get a specialization of struct A<long, int, int, int> which won't match struct A<long, long, long, long> (MSVC seems to be more lenient on this, but GCC behavior is more correct IMO).

Related

CTAD doesn't work with defaulted template arguments?

Compare the following case when I have a class object that takes a vector. The non-deduced parameter T can be substituted fine with the default template argument:
#include <vector>
template <typename T = int>
struct container
{
container(std::vector<T> vec) {}
};
int main()
{
container C = std::vector{1,2,3,4,5};
}
This is not the case for my class which is a bit more complicated (CompilerExplorer):
#include <cstdio>
#include <initializer_list>
#include <variant>
template <size_t> struct obj;
template<size_t Vs>
using val = std::variant<std::monostate, int, struct obj<Vs>>;
template <size_t Vs = 0>
struct obj
{
obj() = default;
obj(std::initializer_list<val<Vs>> init) {
printf("initializer of object called, Vs = %d\n", Vs);
}
};
template <size_t Vs = 0>
struct container : public obj<Vs>
{
container(obj<0> init) {}
};
int main()
{
container<5> some_container = obj{1,2,5,2,obj{1,2,33},2,2};
}
This fails with the following error:
<source>: In function 'int main()':
<source>:29:57: error: class template argument deduction failed:
29 | container<5> some_container = obj{1,2,5,2,obj{1,2,33},2,2};
| ^
<source>:29:57: error: no matching function for call to 'obj(int, int, int)'
<source>:14:5: note: candidate: 'template<long unsigned int Vs> obj(std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >)-> obj<<anonymous> >'
14 | obj(std::initializer_list<val<Vs>> init) {
| ^~~
But it works when I supplement the template specialization obj<0> in the instantiation of the container (in main). Any ideas why this doesn't work for my class and how I can fix it? I don't want to force the user to specify the template each time.
This problem already exists in the simpler case of just
auto o = obj{1,2,33};
which yields this error:
<source>:29:24: error: class template argument deduction failed:
29 | auto o = obj{1,2,33};
| ^
<source>:29:24: error: no matching function for call to 'obj(int, int, int)'
<source>:14:5: note: candidate: 'template<long unsigned int Vs> obj(std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >)-> obj<<anonymous> >'
14 | obj(std::initializer_list<val<Vs>> init) {
| ^~~
<source>:14:5: note: template argument deduction/substitution failed:
<source>:29:24: note: mismatched types 'std::initializer_list<std::variant<std::monostate, int, obj<Vs> > >' and 'int'
29 | auto o = obj{1,2,33};
So, the compiler is unable to deduce, that the three ints should be an initializer list. If you add extra braces around them, the compiler recognizes that this should actually be a single list argument instead of three separate ones and it works:
auto o = obj{{1,2,33}};
This also carries over to the more complicated case:
container some_container = obj{{1,2,5,2,obj{{1,2,33}},2,2}};

SFINAE for unsigned type selection

I'm trying to use SFINAE to check if a type has an unsigned equivalent. While it seems to work for int and bool, it fails for float. From the error it seems a certain type is not defined. The question is if the template argument to enable_if is ill-formed, why isn't this removed from overload selection ?
#include <type_traits>
#include <iostream>
template <typename T>
std::enable_if_t<sizeof(std::make_unsigned<T>), bool> hasUnsigned(T x)
{
return true;
}
bool hasUnsigned(...)
{
return false;
}
int main()
{
float x; // If it's int, or char, it below displays true
std::cout << std::boolalpha << hasUnsigned(x) << std::endl;
}
Error with float
In file included from has_unsigned.cc:1:
/usr/include/c++/10/type_traits: In instantiation of ‘struct std::make_unsigned<float>’:
has_unsigned.cc:5:18: required by substitution of ‘template<class T> std::enable_if_t<(sizeof (std::make_unsigned<_Tp>) != 0), bool> hasUnsigned(T) [with T = float]’
has_unsigned.cc:18:48: required from here
/usr/include/c++/10/type_traits:1826:62: error: invalid use of incomplete type ‘class std::__make_unsigned_selector<float, false, false>’
1826 | { typedef typename __make_unsigned_selector<_Tp>::__type type; };
| ^~~~
/usr/include/c++/10/type_traits:1733:11: note: declaration of ‘class std::__make_unsigned_selector<float, false, false>’
1733 | class __make_unsigned_selector;
| ^~~~~~~~~~~~~~~~~~~~~~~~
You are using make_unsigned on an invalid type (see below) which makes the behavior undefined or program ill-formed. A better approach would be to check if it's an integer:
std::enable_if_t<std::is_integral_v<T>, bool>
From std::make_unsigned:
If T is an integral (except bool) or enumeration type, provides the member typedef type which is the unsigned integer type corresponding to T, with the same cv-qualifiers.
If T is signed or unsigned char, short, int, long, long long; the unsigned type from this list corresponding to T is provided.
If T is an enumeration type or char, wchar_t, char8_t (since C++20), char16_t, char32_t; the unsigned integer type with the smallest rank having the same sizeof as T is provided.
Otherwise, the behavior is undefined. (until C++20)
Otherwise, the program is ill-formed. (since C++20)

Templated class does no compile using the latest intel compiler

The following snippet compiles using the intel icc 2021.2.0 and clang without any warning. But it does not compile icc 2021.3.0 or gcc 11.1 throwing the error
#include <array>
class Base {
public:
enum Types { Al, Mg };
static constexpr unsigned int P = 2;
static constexpr unsigned int bin_Al = 1;
static constexpr unsigned int bin_Mg = 300;
static constexpr std::array<unsigned int, P> bins{bin_Al, bin_Mg};
Base();
};
template <typename Base::Types pNo>
class Derived : public Base {
public:
Derived();
public:
static std::array<double, bins[pNo]> particle_radii;
};
template <typename Base::Types pNo>
std::array<double, Base::bins[pNo]> Derived<pNo>::particle_radii;
Error:
error: conflicting declaration 'std::array<double, pNo->Base::bins.std::array<unsigned int, 2>::operator[]()> Derived<pNo>::particle_radii'
23 | std::array<double, Base::bins[pNo]> Derived<pNo>::particle_radii;
note: previous declaration as 'std::array<double, pNo->Base::bins.std::array<unsigned int, 2>::operator[]()> Derived<pNo>::particle_radii'
19 | static std::array<double, bins[pNo]> particle_radii;
Here is the godbold example.
If I change the std::array to a POD double array, it compiles fine.
Can somebodt explain me why this is not compiling using some compilers?

Is templated alias conducting inner and outer parameter packs non-deduced context?

The problem came from here - I wanted to create an approach solving a little bit more general problem. Consider an example:
#include <utility>
template<class T, std::size_t>
using deduced = T;
template<std::size_t N, class = std::make_index_sequence<N>>
struct Foo;
template<std::size_t N, std::size_t... Is>
struct Foo<N, std::index_sequence<Is...>>{
template <class... Args>
void Bar(deduced<Args, Is>...)
{ }
};
int main() {
Foo<3> myfoo;
myfoo.Bar(true, 2.0f, 3); // OK
//myfoo.Bar(1, 2, 3, 4); // error
}
clang has no problem with compiling the code, gcc on the other hand shows following errors:
prog.cc: In function 'int main()':
prog.cc:18:27: error: no matching function for call to 'Foo<3ul>::Bar(bool, float, int)'
myfoo.Bar(true, 2.0f, 3); // valid
^
prog.cc:12:10: note: candidate: template<class ... Args> void Foo<N, std::integer_sequence<long unsigned int, Is ...> >::Bar(deduced<Args, Is>...) [with Args = {Args ...}; long unsigned int N = 3ul; long unsigned int ...Is = {0ul, 1ul, 2ul}]
void Bar(deduced<Args, Is>...)
^~~
prog.cc:12:10: note: template argument deduction/substitution failed:
prog.cc:18: confused by earlier errors, bailing out
[live demo]
What confuses me gcc does not have a problem with deduction when using the same alias but outer parameter pack isn't involved, e.g.:
void Bar(deduced<Args, 0>...)
So the question is - is it legal to combine parameter packs from outer and inner class with alias of this form to make compiler deduce one of the template parameters or is it gcc bug?
Edit (based on bogdan's comment):
The code causes trouble also to MSVC (2017 RC), but works in this form with EDG compiler, icc (in version 16 and 17) also seem to deal well with a code. It is also worth noting that similar code with class instead of alias (also considered as deduced context in some places example by bogdan) causes even more trouble to compilers - only clang seems to deal well with this version(?)

In-class initialization of std::map

I have this snippet of C++ code that doesn't compile under g++-4.9.1 (I used the command "g++ -c --std=c++11 map.cc")
#include <map>
#include <cstdint>
class A {
std::map<uint8_t, uint8_t> b = std::map<uint8_t, uint8_t>();
};
I get the following error when compiling:
map.cc:5:52: error: expected ‘;’ at end of member declaration
std::map<uint8_t, uint8_t> b = std::map<uint8_t, uint8_t>();
^
map.cc:5:52: error: declaration of ‘std::map<unsigned char, unsigned char> A::uint8_t’ [-fpermissive]
In file included from /usr/lib/gcc/x86_64-linux-gnu/4.9/include/stdint.h:9:0,
from /usr/include/c++/4.9/cstdint:41,
from /usr/include/c++/4.9/bits/char_traits.h:380,
from /usr/include/c++/4.9/string:40,
from /usr/include/c++/4.9/stdexcept:39,
from /usr/include/c++/4.9/array:38,
from /usr/include/c++/4.9/tuple:39,
from /usr/include/c++/4.9/bits/stl_map.h:63,
from /usr/include/c++/4.9/map:61,
from map.cc:1:
/usr/include/stdint.h:48:24: error: changes meaning of ‘uint8_t’ from ‘typedef unsigned char uint8_t’ [-fpermissive]
typedef unsigned char uint8_t;
^
map.cc:5:59: error: expected unqualified-id before ‘>’ token
std::map<uint8_t, uint8_t> b = std::map<uint8_t, uint8_t>();
^
map.cc:5:43: error: wrong number of template arguments (1, should be 4)
std::map<uint8_t, uint8_t> b = std::map<uint8_t, uint8_t>();
^
In file included from /usr/include/c++/4.9/map:61:0,
from map.cc:1:
/usr/include/c++/4.9/bits/stl_map.h:96:11: error: provided for ‘template<class _Key, class _Tp, class _Compare, class _Alloc> class std::map’
class map
^
However, if I replace uint8_t with int, it compiles fine.
The problem with g++ is much larger, whenever you use a template as a class member, you cannot use member initialization if any parameter (except the first one), is a typedef or in another namespace.
typedef int I;
template<typename T1, typename T2> struct A {};
struct B {
A<I,float> a1=A<I,float>(); // works!
A<float,I> a2=A<float,I>(); // does not compile!
// This is the same reason the map does not comile, as string is a typedef
};
FWIW, if you need a work around, the following works:
class A {
typedef std::map<uint8_t, uint8_t> B;
B b = B();
};