I just upgraded to GCC 4.8 and some variadic template code no longer compiles correctly. I've created a minimal example below:
#include <tuple>
#include <iostream>
template <class T, class ... OtherT>
void something( std::tuple<T, OtherT...> & tup )
{
std::cout << std::get<1>(tup) << std::endl;
}
int main()
{
std::tuple<int, char, bool> myTuple(3, 'a', true);
// Compiles OK in GCC 4.6.3 but NOT 4.8
something<int, char, bool>( myTuple );
// Compiles OK in GCC 4.8 but NOT 4.6.3
something<int, bool, char>( myTuple );
return 0;
}
The output of this will be (if commenting out the incorrect version for GCC 4.6.3/4.8)
'a'.
The error produced by GCC 4.6.3 is:
./test.cpp: In function ‘int main()’:
./test.cpp:18:39: error: no matching function for call to ‘something(std::tuple<int, char, bool>&)’
./test.cpp:18:39: note: candidate is:
./test.cpp:5:6: note: template<class T, class ... OtherT> void something(std::tuple<_Head, _Tail ...>&)
The error produced by GCC 4.8 is:
./test.cpp: In function ‘int main()’:
./test.cpp:15:39: error: no matching function for call to ‘something(std::tuple<int, char, bool>&)’
something<int, char, bool>( myTuple );
^
./test.cpp:15:39: note: candidate is:
./test.cpp:5:6: note: template<class T, class ... OtherT> void something(std::tuple<_El0, _El ...>&)
void something( std::tuple<T, OtherT...> & tup )
^
./test.cpp:5:6: note: template argument deduction/substitution failed:
./test.cpp:15:39: note: mismatched types ‘bool’ and ‘char’
something<int, char, bool>( myTuple );
It seems like in GCC 4.8, variadic template types are reversed when expanded, though oddly enough they don't "really" get reversed as proved by the output - it will be 'a' regardless of the ordering. Clang 3.3 agrees with the GCC 4.6.3 output.
Is this a bug in GCC 4.8 or something else?
EDIT: added a bug report to GCC here: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56774
This looks like a bug to me, GCC 4.8.0 and GCC 4.7.2 seem to be affected. Clang 3.2 and GCC 4.6.3 agree that the first call to something is the correct one and I really fail to see how GCC 4.7.2+ can consider the second call to be acceptable.
I'd say: Report a bug against GCC.
Update: I added a minimalistic example to the GCC bug report, just to help them and prove that it's a pure compiler bug and has nothing to do with std::tuple. Here's the reduced code:
template< typename... > struct X {};
template< typename T, typename... Ts >
void f( X< T, Ts... >& ) {}
int main()
{
X< int, bool, char > t;
f< int, char, bool >(t);
}
Update 2: It's now fixed for GCC 4.7.3, GCC 4.8.1 and GCC 4.9 - kudos to the GCC team for an insanely fast fix!
Related
The following code compiles fine with the latest version of Visual Studio Comumnity 16.5.4 configured to use the latest version of the C++ standard however it fails to compile on G++ 9.2 with a very cryptic and unhelpful error message.
#include <iostream>
#include <utility>
namespace Aux {
template <std::size_t INDEX, typename TYPE>
struct TypeValue {
static TYPE get(std::in_place_index_t<INDEX>);
};
template <std::size_t INDEX, typename... TYPES>
struct TGetType;
template <std::size_t INDEX, std::size_t... INDICES, typename... TYPES>
struct TGetType<INDEX, std::index_sequence<INDICES...>, TYPES...>
: TypeValue<INDICES, TYPES>... {
using TypeValue<INDICES, TYPES>::get...;
using Type = decltype(get(std::in_place_index_t<INDEX>()));
};
}
template <std::size_t INDEX, typename... TYPES>
struct TGetType {
static_assert(INDEX < sizeof...(TYPES));
using Type = typename Aux::TGetType<INDEX, std::index_sequence_for<TYPES...>, TYPES...>::Type;
};
template <std::size_t INDEX, typename... TYPES>
using GetType = typename TGetType<INDEX, TYPES...>::Type;
template <std::size_t INDEX, typename... TYPES>
inline constexpr auto get_type = GetType<INDEX, TYPES...>();
int
main() {
using T = GetType<1, int, double, bool>;
}
Here's the error message:
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp: In instantiation of 'struct Aux::TGetType<1, std::integer_sequence<long long unsigned int, 0, 1, 2>, int, double, bool>':
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp:24:8: required from 'struct TGetType<1, int, double, bool>'
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp:28:7: required by substitution of 'template<long long unsigned int INDEX, class ... TYPES> using GetType = typename TGetType::Type [with long long unsigned int INDEX = 1; TYPES = {int, double, bool}]'
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp:35:40: required from here
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp:17:28: error: cannot convert 'in_place_index_t<1>' to 'in_place_index_t<2>'
17 | using Type = decltype(get(std::in_place_index_t<INDEX>()));
| ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\joaom\Dropbox\++A\tests\gcc-err.cpp:7:19: note: initializing argument 1 of 'static TYPE Aux::TypeValue<INDEX, TYPE>::get(std::in_place_index_t<_Idx>) [with long long unsigned int INDEX = 2; TYPE = bool]'
7 | static TYPE get(std::in_place_index_t<INDEX>);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Finished in 0.4s]
I've installed G++ through https://nuwen.net/mingw.html and it came with the following configuration:
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=c:/program\ files/mingw-w64/mingw-17.1-without-git/bin/../libexec/gcc/x86_64-w64-mingw32/9.2.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../src/configure --enable-languages=c,c++ --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --disable-multilib --prefix=/c/temp/gcc/dest --with-sysroot=/c/temp/gcc/dest --disable-libstdcxx-pch --disable-libstdcxx-verbose --disable-nls --disable-shared --disable-win32-registry --with-tune=haswell --enable-threads=posix --enable-libgomp
Thread model: posix
gcc version 9.2.0 (GCC)
Haven't tried yet with Clang since the latest version of Visual Studio C++ libraries aren't yet compatible with Clang:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include\concepts:16:2: error: Despite the presence of some Clang-related bits, this header currently does not support Clang. You can define _SILENCE_CLANG_CONCEPTS_MESSAGE to silence this message and acknowledge that this is unsupported.
#error Despite the presence of some Clang-related bits, this header currently does not support Clang. \
Is my code standard compliant or am I relying on MSC extended behaviour? I really don't see anything strange or wrong with it, and more complex cases of metaprogramming similar to that one seems to be working fine on Visual Studio.
The following code:
variant<string> x = "abc";
cout << get<string>(x) << "\n";
works fine under g++ (version 7.2). However, when compiled under clang++ (version 5.0) using libstdc++, I get the following error in the get method:
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../include/c++/7.2.0/variant:238:46: fatal error: cannot cast 'std::variant<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >' to its private base class 'std::__detail::__variant::_Variant_storage<false, std::
__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'
return __get(std::in_place_index<_Np>, std::forward<_Variant>(__v)._M_u);
Is this a compiler bug, or is my code illegal in any way?
This is caused by clang bug 31852 (and also 33222), whose reproduction courtesy of Jonathan Wakely should look very relevant:
template<typename V> auto get(V&) { }
template<typename>
class variant
{
template<typename V> friend auto get(V&);
};
int main()
{
variant<int> v{};
get(v); // error: ambiguous
}
clang doesn't properly recognize friend declarations that have placeholder types. Which is exactly how libstdc++ implements std::get:
// Returns the typed storage for __v.
template<size_t _Np, typename _Variant>
constexpr decltype(auto) __get(_Variant&& __v)
{
return __get(std::in_place_index<_Np>, std::forward<_Variant>(__v)._M_u);
}
this accesses a private member of variant, but this function is properly declared a friend:
template<size_t _Np, typename _Vp>
friend constexpr decltype(auto) __detail::__variant::__get(_Vp&& __v);
libstdc++'s implementation is valid, clang just doesn't think __get is a friend.
The following snippet does not compile with clang, but with g++ (details below):
#include <utility>
struct S {
template<typename T>
void operator()(T&& /*t*/) {}
template<typename T0, typename T1, typename... Args>
auto operator()(T0&& t0, T1&& /*t1*/, Args&&... args) ->
decltype(operator()(std::forward<T0>(t0), std::forward<Args>(args)...))
{}
};
int main(int /*argc*/, char** /*argv[]*/) {
S()(1, 2);
S()(1, 1.2, 1.2); // does not compile with clang
}
I.e. the variadic operator() should recurse until it reaches the base case.
When I compile this with gcc 7.2.0 (g++ -std=c++1y), it compiles. But with clang 3.8.0 (clang++ -std=c++1y), I obtain the following compilation failure:
main.cpp:15:5: error: no matching function for call to object of type 'S'
S()(1, 1.2, 1.2); // does not compile with clang
^~~
main.cpp:8:10: note: candidate template ignored: substitution failure [with T0 = int, T1 = double, Args = <double>]: no matching member function for call to 'operator()'
auto operator()(T0&& t0, T1&& /*t1*/, Args&&... args) ->
^
main.cpp:5:7: note: candidate function template not viable: requires 1 argument, but 3 were provided
void operator()(T&& /*t*/) {}
Is this a bug in one of the compilers or am I possibly hitting undefined behaviour somewhere?
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(?)
Here's a sample program:
#include <type_traits>
#include <stdio.h>
template <typename X>
struct test
{
operator int() const { puts("?"); return 0; }
template <typename T, typename = typename std::enable_if<std::is_same<X, void*>::value, T>::type>
operator T() const { puts("T"); return 0; }
};
int main()
{
test<void*> t;
char* c = (char*)t;
switch (t)
{
case 0: break;
}
return 0;
}
And this is the error that g++-4.7 gives
user#user:~$ g++-4.7 -std=c++0x test.cpp
test.cpp: In function ‘int main()’:
test.cpp:13:14: error: ambiguous default typeconversion from ‘test<void*>’
test.cpp:13:14: error: candidate conversions include ‘template<class T, class> test::operator void*() const [with T = T; <template-parameter-2-2> = <template-parameter-1-2>; X = void*]’
g++ 4.6 compiles it without errors and different operators are actually called.
Is there a way to make this work under g++ 4.7?
UPDATE: actually it works in 4.6 without any enable_if at all... so the question still applies but I'm now not sure if enable_if will help.
If you add an explicit cast to int here:
switch ((int)t)
Then it should compile.
I think it's complaining about the conversion being ambiguous since there exists more than one type that can hold a 0 value.
I'm using g++ 4.8 though.
I found at least one "acceptable" solution:
#define switch(x) \
switch( (typename switch_type<__typeof(x)>::type)(x) )
which switch_type trait can be extended to resolve ambiguity for specific app-related types (property types).