Incorrect template substitution error with g++8.3 - c++

I have the following program -
class foo {
typedef foo type;
};
template<typename A>
using type_t = typename A::type;
template <typename T>
type_t<T> bar(void){
return type_t<T>();
}
int main() {
bar<foo>();
return 0;
}
It is malformed because you cannot access foo::type inside bar because it is private (if I make type public, the program compiles fine.)
But when I try to compile this with g++8.3, I get the following error -
prog.cpp: In function ‘int main()’:
prog.cpp:16:11: error: no matching function for call to ‘bar<foo>()’
bar<foo>();
^
prog.cpp:11:11: note: candidate: ‘template<class T> type_t<T> bar()’
type_t<T> bar(void){
^~~
prog.cpp:11:11: note: template argument deduction/substitution failed:
prog.cpp: In substitution of ‘template<class A> using type_t = typename A::type [with A = foo]’:
prog.cpp:8:32: recursively required by substitution of ‘template<class A> using type_t = typename A::type [with A = foo]’
prog.cpp:8:32: required by substitution of ‘template<class A> using type_t = typename A::type [with A = foo]’
prog.cpp:11:11: required by substitution of ‘template<class T> type_t<T> bar() [with T = foo]’
prog.cpp:16:11: required from here
prog.cpp:8:32: fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
using type_t = typename A::type;
Live here on Ideone
What is happening here? Is this a compiler bug or am I not understanding something correctly? Is the typedef being marked as private cause SFINAE to kick in somehow causing it to look for alternate substitutions that are recursive?
Btw, I get the correct error about type being private if I don't use the type alias type_t and directly define the function bar as -
template <typename T>
typename T::type bar(void) {
return typename T::type();
}
The error also has nothing to do with the fact that foo::type is foo. If I change the typedef to typedef int type, I still get the same error about recursive substitution.

Related

How to explicitly tell compiler to choose exactly one parameter template during template pack expansion?

I'm trying using pack parameter template to fit some cases. I want to stop the template packed parameter expanding when there is only one parameter in the list. I want to use the typename explicitly when instancing the template, rather than using the typename to declare variables.
Here is a minimal example:
template <class T>
void f() {}
template <class T, class... Args>
void f() {
f<Args...>();
}
int main() {
f<int, int, double>();
return 0;
}
When compiling the code, I got the error:
demo.cc: In instantiation of ‘void f() [with T = int; Args = {double}]’:
demo.cc:6:13: required from ‘void f() [with T = int; Args = {int, double}]’
demo.cc:10:23: required from here
demo.cc:6:13: error: call of overloaded ‘f<double>()’ is ambiguous
6 | f<Args...>();
| ~~~~~~~~~~^~
demo.cc:2:6: note: candidate: ‘void f() [with T = double]’
2 | void f() {}
| ^
demo.cc:5:6: note: candidate: ‘void f() [with T = double; Args = {}]’
5 | void f() {
| ^
I have read the following information from cppreferrence here:
A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order.
It may be why the compiler can't decide whether to use void f() [with T = double] or void f() [with T = double; Args = {}]
Can I stop the unpacking only by using template parameters rather than using input argument of function?
You can constrain the variadic version of the function with SFINAE to stop it from being called if the parameter pack is empty. That would look like
template <class T, class... Args, std::enable_if_t<(sizeof...(Args) > 0), bool> = true>
void f() {
f<Args...>();
}

What's the best way to notify about malformed template instantiations?

How can I make forbidden template instantiations clearly visible at compile time? I was thinking about throwing an exception by inheriting it:
template<string message> struct exception {};
template<class T> struct non_void;
template<class T> using non_void_t = typename non_void<T>::type;
template<> struct non_void<void>: exception<string{"non_void: argument is void"}> {};
non_void_t<void> poorly_typed_object;
but gcc 10 simply complains aboue some error:
$ g++ -std=c++20 exc.cpp
exc.cpp: In substitution of ‘template<class T> using non_void_t = typename non_void::type [with T = void]’:
exc.cpp:11:16: required from here
exc.cpp:7:25: error: no type named ‘type’ in ‘struct non_void<void>’
7 | template<class T> using non_void_t = typename non_void<T>::type;
and does not display anything about the erroneus instantiation's parent type, so the actual error message never appears in compiler's output.
Is there a way to make it more apparent?
I would suggest making the failing specialization non_void_t<void> a constrained partial specialization that triggers a static_assert on instantiation:
#include <type_traits>
template<class T> concept Void = std::is_void_v<T>;
template<Void T> struct non_void<T> { static_assert(!std::is_void_v<T>, "argument is void"); };
Example.

dependent-name T is parsed as a non-type, but instantiation yields a type

I realize that there have been some questions on SO touching this subject but I haven't found the solution yet.
I would like to enable function f only when all of its arguments are pod.
I have the following piece of code to do this:
#include <type_traits>
template <typename... Conds>
struct and_ : std::true_type
{
};
template <typename Cond, typename... Conds>
struct and_<Cond, Conds...> :
std::conditional<Cond::value, and_<Conds...>, std::false_type>::type
{
};
template <typename... T>
using are_all_pod = and_<std::is_pod<T>...>;
template<typename... Args,
typename = typename std::enable_if<are_all_pod<Args...>::type>>
void f(Args ... args)
{
}
int main()
{
f(1,2);
}
I do receive the following error:
main.cpp: In function ‘int main()’:
main.cpp:25:8: error: no matching function for call to ‘f(int, int)’
f(1,2);
^
main.cpp:19:6: note: candidate: template<class ... Args, class> void f(Args ...)
void f(Args ... args)
^
main.cpp:19:6: note: template argument deduction/substitution failed:
main.cpp:18:10: error: dependent-name ‘and_<std::is_pod<T>...>::type’ is parsed as a non-type, but instantiation yields a type
typename = typename std::enable_if<are_all_pod<Args...>::type>>
^
main.cpp:18:10: note: say ‘typename and_<std::is_pod<T>...>::type’ if a type is meant
I tried following compiler's advice but I get the same error then. Using gcc 5.2.1
std::enable_if<are_all_pod<Args...>::type> makes no sense.
Change it to something like std::enable_if<are_all_pod<Args...>::value, void>::type and the program will compile.

How to force template substitution failure unless T::answer is in integral type?

I have the following code:
#include <type_traits>
struct SA {};
struct SB { static const int answer = 42; };
const int SB::answer;
template <typename T>
int F() {
return T::answer;
}
int main(int argc, char **argv) {
(void)argc; (void)argv;
// F<SA>(); // I want to make this a template substitution failure.
return F<SB>(); // This should still work.
}
I want to make the call F<SA>() become a template substitution failure. I've tried changing int F to typename std::enable_if<std::is_integral<T::answer>::value, int>::type F, but I got the following error:
$ g++ --versiong++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ g++ -W -Wall -Wextra -Werror -std=c++0x -fno-diagnostics-show-caret ei.cc && ./a.out
ei.cc:8:55: error: to refer to a type member of a template parameter, use ‘typename T:: answer’ [-fpermissive]
ei.cc: In function ‘int main(int, char**)’:
ei.cc:16:20: error: no matching function for call to ‘F()’
ei.cc:16:20: note: candidate is:
ei.cc:8:76: note: template<class T> typename std::enable_if<std::is_integral<typename T::answer>::value, int>::type F()
ei.cc:8:76: note: template argument deduction/substitution failed:
ei.cc: In substitution of ‘template<class T> typename std::enable_if<std::is_integral<typename T::answer>::value, int>::type F() [with T = SB]’:
ei.cc:16:20: required from here
ei.cc:8:76: error: no type named ‘answer’ in ‘struct SB’
This is trying to look for a type named answer in struct SB, but I want to make it look for an integral field named answer in struct SB.
I find putting the enable_if check in the template arguments makes your interface a bit clearer:
template <typename T,
std::enable_if_t<std::is_integral<decltype(T::answer)>::value>* = nullptr>
int F() {
return T::answer;
}
Or even clearer, using R. Martinho Fernandes' Remastered enable_if.
namespace detail {
enum class enabler {};
}
template <typename Condition>
using EnableIf = std::enable_if_t<Condition::value, detail::enabler>;
template <typename T, EnableIf<std::is_integral<decltype(T::answer)>>...>
int F() {
return T::answer;
}
Adding decltype(...) seems to work: changing int F to typename std::enable_if<std::is_integral<decltype(T::answer)>::value, int>::type F.

Does SFINAE not apply here?

I was writing something to use SFINAE to not generate a function under certain conditions. When I use the meta code directly it works as expected, but when I use the code indirectly through another class, it fails to work as expected.
I thought that this was a VC++ thing, but looks like g++ also has this, so I'm wondering if there's some reason that SFINAE isn't being applied to this case.
The code is simple. If the class used isn't the base class of a "collection class", then don't generate the function.
#include <algorithm>
#include <type_traits>
#define USE_DIRECT 0
#define ENABLE 1
class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};
#if USE_DIRECT
template<typename X>
typename std::enable_if<std::is_base_of<X, collection1>::value, X>::type fn(X x)
{
return X();
}
# if ENABLE
template<typename X>
typename std::enable_if<std::is_base_of<X, collection2>::value, X>::type fn(X x)
{
return X();
}
# endif
#else // USE_DIRECT
template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
static const int value = std::is_base_of<X, COLLECTION>::value;
typedef typename std::enable_if<value, X>::type type;
};
template<typename X>
typename enable_if_is_base_of<X, collection1>::type fn(X x)
{
return X();
}
# if ENABLE
template<typename X>
typename enable_if_is_base_of<X, collection2>::type fn(X x)
{
return X();
}
# endif
#endif // USE_DIRECT
int main()
{
fn(A());
fn(B());
fn(C());
fn(D());
return 0;
}
If I set USE_DIRECT to 1 and ENABLE to 0, then it fails to compile as there is no function fn that takes a parameter D. Setting ENABLE to 1 will stop that error from occurring.
However, if I set USE_DIRECT to 0 and ENABLE to 0, it will fail with different error messages but for the same case, there is no fn that takes parameter D. Setting ENABLE to 1 however will cause a failure for all 4 function calls.
For your convience, here is the code in an online compiler: http://goo.gl/CQcXHr
Can someone explain what is happening here and why?
This looks like it may be related to Alias templates used in SFINAE lead to a hard error, but no one has answered that either.
For reference, here are the errors that were generated by g++:
main.cpp: In instantiation of 'struct enable_if_is_base_of<A, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = A]'
main.cpp:54:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, A>'
typedef typename std::enable_if<std::is_base_of<X, COLLECTION>::value, X>::type type;
^
main.cpp: In instantiation of 'struct enable_if_is_base_of<B, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = B]'
main.cpp:55:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, B>'
main.cpp: In instantiation of 'struct enable_if_is_base_of<C, collection2>':
main.cpp:45:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = C]'
main.cpp:56:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, C>'
main.cpp: In instantiation of 'struct enable_if_is_base_of<D, collection1>':
main.cpp:38:53: required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection1>::type fn(X) [with X = D]'
main.cpp:57:8: required from here
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, D>'
Only a substitution that takes place in an immediate context may result in a deduction failure:
§14.8.2 [temp.deduct]/p8
Only invalid types and expressions in the immediate context of the function type and
its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types
and expressions can result in side effects such as the instantiation of class template specializations and/or
function template specializations, the generation of implicitly-defined functions, etc. Such side effects are
not in the “immediate context” and can result in the program being ill-formed. — end note ]
The signature of fn requires a full declaration of enable_if_is_base's specialization to exist:
§14.7.1 [temp.inst]/p1
Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3),
the class template specialization is implicitly instantiated when the specialization is referenced in a context
that requires a completely-defined object type or when the completeness of the class type affects the semantics
of the program.
The compiler fails to generate the specialization of:
template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
static const int value = std::is_base_of<X, COLLECTION>::value;
typedef typename std::enable_if<value, X>::type type;
};
because the substitution of typename std::enable_if<value, X>::type results in a missing type if value evaluates to false, which is not in an immediate context.
As already answered your SFINAE scheme can't work. However, you could use the following not so pretty solution to achieve what you probably want:
#include <algorithm>
#include <type_traits>
#include <iostream>
class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};
template<typename X, class Enable = void>
struct enable_if_is_base_of;
template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection1>::value>::type> {
static X fn(X x) {
(void) x;
std::cout << "collection1" << std::endl;
return X();
}
};
template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection2>::value>::type> {
static X fn(X x) {
(void) x;
std::cout << "collection2" << std::endl;
return X();
}
};
int main() {
enable_if_is_base_of<A>::fn(A());
enable_if_is_base_of<B>::fn(B());
enable_if_is_base_of<C>::fn(C());
enable_if_is_base_of<D>::fn(D());
}
LIVE DEMO
The problem is that if the type doesn't exist, the typedef isn't skipped, it is an error.
Solution: no typedef
template<typename X, typename COLLECTION>
struct enable_if_is_base_of : std::enable_if<std::is_base_of<X, COLLECTION>::value, X>
{};
Now the type member exists (via inheritance) if and only if it exists within the enable_if instantiation.