Code
The following code gives different output with and without the line marked with * commented:
#include <iostream>
#include <type_traits>
template <bool>
using bool_void_t = void;
template <typename, typename = void>
struct is_complete : std::false_type
{
};
template <typename T>
struct is_complete<T, bool_void_t<sizeof(T) == sizeof(T)>> : std::true_type
{
};
template <typename Derived>
struct Base
{
static constexpr bool value = is_complete<Derived>{};
// using magic = bool_void_t<value>; // *
};
struct Foo : Base<Foo>
{
};
int main()
{
std::cout << std::boolalpha << Foo::value << std::endl;
}
Output
The line marked with * is commented: true.
The line marked with * is not commented: false.
Compiler and its flags
In both cases clang++ 5.0.0 is used as a compiler and the compiler flags are -std=c++17 -Wall -Wextra -Werror -pedantic-errors.
More advanced research
clang++ 5.0.0 (-Wall -Wextra -Werror -pedantic-errors)
-std=c++14 -std=c++17
* is commented false true
* is not commented false false
g++ 7.2.1 (-Wall -Wextra -Werror -pedantic-errors)
-std=c++14 -std=c++17
* is commented true true
* is not commented false false
Questions
Is such behavior of the compilers standard compliant? If yes, what rationale stands behind it?
What differences between C++14 and C++17 could lead to the difference in the observed behavior using the clang++ compiler when the line marked with * is commented (the output is false with the -std=c++14 compiler flag and true with the -std=c++17 one)?
A common problem encountered when using CRTP is that when the base class is instantiated, the derived class is not complete. This means you cannot use member typedefs in the derived class, among other things.
This makes sense when you think about it: a template class is really a way to generate a new class type based on the given template types, so until the compiler reaches the closing } (in an approximate sense), the base class isn't fully defined. If the base class isn't fully defined, then obviously the derived class can't be either.
Because both the base and derived class are empty (in the first example), the compiler considers them complete. I would say this is incorrect, but I'm not an expect and can't be sure. Still, the trick here is that you are instantiating the value of is_complete while defining the base class. After the derived class is fully defined, it will be complete.
Also, for an example of where this matters, consider something like this:
template <typename>
class crtp_traits;
class derived;
template <>
class crtp_traits<derived>
{
public:
using return_type = int;
};
template <typename T>
class base
{
public:
auto get_value() const -> typename crtp_traits<T>::return_type
{
return static_cast<T const*>(this)->do_get_value();
}
};
class derived : public base<derived>
{
public:
auto do_get_value() const -> int
{
return 0;
}
};
The simple solution of giving derived a member typedef using return_type = int; will not work because derived won't be complete by the time base tries to access the typedef.
Turning out my comment into answer:
I think that is_complete make the code ill-formed (No diagnostic required)...
Its value might depends where it is instantiated and breaks ODR.
class A; // A not complete yet
static_assert(!is_complete<A>::value);
class A{}; // Now it is
static_assert(is_complete<A>::value);
From dependent_name
If the meaning of a non-dependent name changes between the definition context and the point of instantiation of a specialization of the template, the program is ill-formed, no diagnostic required. This is possible in the following situations:
a type used in a non-dependent name is incomplete at the point of definition but complete at the point of instantiation.
That seems to be the case for magic.
Even when magic is commented, following should also make code ill formed:
static constexpr bool value = is_complete<Derived>{};
Related
I found that the code snippet below compiles fine on gcc 11, clang, and MSVC, but starting with gcc 12.1, it gives the error:
using Y = typename Derived::template X<1>; // offending line
error: 'class Derived<T>::X<1>' resolves to 'Base<double>::X<1>' {aka 'double'}, which is not a class type
Why does the alias need to resolve to a class type for this to be a properly formed alias?
Minimal Example Code (godbolt):
#include <iostream>
template <typename T>
struct Base {
template <int I>
using X = T;
};
template <typename T>
struct Derived : public Base<T> {
using Y = typename Derived::template X<1>;
};
struct O {};
int main() {
std::cout << typeid(Derived<double>::Y).name() << std::endl; // error
std::cout << typeid(Derived<O>::Y).name() << std::endl; // works
}
In the code in question, I'm trying to create an alias to a parent's (templated) alias, but it only works when the alias resolves to a class type. Of course, something like using Y = double; is perfectly legal, and when any of Base, Derived, or X are not templated it also seems to work fine, so how come this particular combination of dependent types makes it so that X<something> is no longer allowed to resolve to e.g. arithmetic types, and only on gcc 12.1 and up?
I skimmed through the gcc 12 change list but didn't see anything that struck out to me as related.
(I'm using c++11 but it that doesn't seem to be relevant as it also occurs when specifying c++14, 17, 0x)
I met a weird behaviour when implementing a SFINAE class.
I want to use SFINAE to allow several types to be implemented. And disallow other types
template<typename T>
class Base
{
public:
using Enable = typename std::enable_if_t<std::is_same<T, std::string>::value>;
std::tuple<T> data; //(1)
};
class NotAllowedType{};
class A: public Base<std::string>{}; //(2)
class B: public Base<NotAllowedType>{};
int main()
{
B b;
}
The thing is, it can pass compile.
If (1) or (2) commented, it fails the compile as expected
I uses g++ -std=c++17
I was learning about SFINAE and how to easy implement it with void_t. But I get different output for different compilers:
//pre c++17 void_t definition:
template<class... Ts> struct make_void {typedef void type;};
template<class... Ts> using void_t = typename make_void<Ts...>::type;
//check for member helper structures
template<class, class = void>
struct has_abc : std::false_type
{ };
template<class T>
struct has_abc<T, void_t<decltype(T::abc)>> : std::true_type
{ };
class has
{
public:
void abc();
};
class has_not
{ };
int main()
{
std::cout << has_abc<has>::value << std::endl;
std::cout << has_abc<has_not>::value << std::endl;
}
GCC 5.3.0 prints expected output 1 0, but MSVC 2015 prints 0 0, why?
EDIT:
Additional example with working GCC 5.3.0 code that supposedly violates c++ syntax:
template<class T>
void test()
{
std::cout << std::is_same<decltype(T::func), void(T::*)(void)>::value << std::endl;
}
class Test
{
public:
void func();
};
int main()
{
test<Test>();
}
Output:
1
In fact there is an error with your code. MSVC is right while GCC is simply wrong.
The syntax for pointer to member function doesn't work like that. You have to put the & in front of the expression:
//check for member helper structures
template<class, class = void>
struct has_abc : std::false_type {};
template<class T>
struct has_abc<T, void_t<decltype(&T::abc)>> : std::true_type {};
// Notice the '&' there ------^
The syntax for T::member only work with static data member, and typename T::member work for member types. While working with sfinae, it's important to distinguish small syntax properties and differences.
As a request in the comment, here's multiple statement that shows that non static member cannot be referenced without the & with GCC 5.3: https://godbolt.org/g/SwmtG2
Here's an example with GCC: http://coliru.stacked-crooked.com/a/0ee57c2c34b32753
Here's an example with MSVC: http://rextester.com/FJH22266
Here's the section from the C++ standard that explicitly state that without the &, the expression is not well formed:
[expr.prim.id]/2
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
as part of a class member access ([expr.ref]) in which the object expression refers to the member's class or a class derived from that class,
or to form a pointer to member ([expr.unary.op]), or
if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [ Example:
-
struct S {
int m;
};
int i = sizeof(S::m); // OK
int j = sizeof(S::m + 42); // OK
— end example ]
As we discussed in the chat, we concluded that the reason there is a difference with the two compilers is that both GCC and MSVC has bugs that prevent this code to work well. As mentionned, MSVC will refuse to apply the SFINAE rule if there is an unrelated class that don't implement the rule correctly: http://rextester.com/FGLF68000
Note that sometime, changing the name of a type trait helped MSVC to parse my code correctly, but it's mostly unreliable.
Consider reporting a bug to microsoft and upgrade your GCC version if you want your code to work as expected.
this way works:
template<class T>
struct has_abc<T, void_t<decltype(std::declval<T>().abc())>> : std::true_type
{ };
I don't understand why in the following code, I am allowed to create the function print_private_template while the compiler complains about print_private_class:
#include <cstdio>
class A
{
private:
template <unsigned T>
struct B
{
};
struct C
{
};
public:
template <unsigned T>
B<T> getAb()
{
return B<T>();
}
C getAc()
{
return C();
}
};
template<unsigned T>
void print_private_template(const A::B<T> &ab)
{
printf("%d\n", T);
}
void print_private_class(const A::C &ac)
{
printf("something\n");
}
int main(int, char**)
{
A a;
print_private_template(a.getAb<42>());
print_private_class(a.getAc());
return 0;
}
Is this an expected behaviour? a compiler bug/extension?
Just to be clear, my goal is to make the compiler error on both the usage of print_private_template and print_private_class.
Comeau does give an error (when you comment out the print_private_class function and its call in strict C++03 mode.
ComeauTest.c(31): error: class template "A::B" (declared at line 7) is inaccessible
void print_private_template(const A::B &ab)
^
detected during instantiation of "print_private_template" based on
template argument <42U> at line 45
G++ 4.5 on Windows does not report any error with -std=c++ -Wall -pedantic though.
Your class A::C and class template A::B<T> both have the same visibility as any other normal members. Hence, both print_private_class and print_private_template require a diagnostic.
11.8 Nested classes [class.access.nest]
1 A nested class is a member and as such has the same access rights as any other member. The members of
an enclosing class have no special access to members of a nested class; the usual access rules (Clause 11)
shall be obeyed.
As stated by Dirk Gently, GCC doesn't perform access control when instantiating template structs / classes nested in other (template) structs / classes.
One way to work around this is to encapsulate them in a non-template struct:
template<int I> class MyTemplate
{
struct PT
{
template<int, typename = void> struct InnerTemplate;
// ... specialisations here ...
};
public:
typedef typename PT::template InnerTemplate<I>::SomeType SomeType;
};
typedef MyTemplate<1>::PT::InnerTemplate<1> ThisWontWork;
The last line will fail to compile with the error:
error: 'struct MyTemplate<1>::PT' is private within this context
I'll grant that this is ugly, especially having to use PT::template but it seems to effectively prevent clients from instantiating helper templates they aren't meant to access, so it's worth a shot.
It got fixed for GCC 11
Ten years later... and the bug got fixed for GCC 11: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41437#c13 (another point in the dupe pool had been previously linked to by dirkgently).
A minimal reproduction:
main.cpp
class Out {
protected:
class In {};
};
template <class C>
void f() { Out::In in; }
int main() {
f<Out>();
}
still compiles in GCC 10.2 with all warnings enabled:
g++-10 -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
but fails correctly in clang++ 10:
void f() { Out::In in; }
^
main.cpp:3:11: note: declared protected here
class In {};
^
1 error generated
The above fails to fail in GCC because f is a template function.
EDIT: This is not a bug, just me not knowing about dependent name lookups in templated base classes (which MSVC "helpfully" resolves without errors).
I wrote a functor implementation a while back, and a simple "Event" wrapper that uses it. It compiles fine under MSVC, but GCC gives an error about a member variable in the base class, subscribers, not being declared; changing subscribers to this->subscribers resolves the issue(!). It appears to happen only with the curiously recurring template pattern, and with partial template specialization.
Simplified source (sorry for the mind-bending template usage...):
#include <vector>
template<typename TEvent>
struct EventBase
{
protected:
std::vector<int> subscribers;
};
template<typename TArg1 = void, typename TArg2 = void>
struct Event : public EventBase<Event<TArg1, TArg2> >
{
void trigger(TArg1 arg1, TArg2 arg2) const
{
// Error on next line
auto it = subscribers.cbegin();
}
};
template<typename TArg1>
struct Event<TArg1, void> : public EventBase<Event<TArg1> >
{
void trigger(TArg1 arg1) const
{
// Using `this` fixes error(?!)
auto it = this->subscribers.cbegin();
}
};
template<>
struct Event<void, void> : public EventBase<Event<> >
{
void trigger() const
{
// No error here even without `this`, for some reason!
auto it = subscribers.cbegin();
}
};
int main()
{
return 0;
}
Am I invoking undefined behaviour somewhere? Is my syntax somehow wrong? Is this really a bug in GCC? Is it perhaps a known bug? Any insight would be appreciated!
More details: Compiled using g++ -std=c++11 main.cpp. I'm using GCC version 4.7.2. Exact error message:
main.cpp: In member function ‘void Event<TArg1, TArg2>::trigger(TArg1, TArg2) const’:
main.cpp:17:15: error: ‘subscribers’ was not declared in this scope
This is a bug in MSVC instead. Names from dependent base classes have to be "thisambiguated".
The reason is that unqualified lookup of dependent names proceeds in two phases. During the first phase, the base class is not yet known and the compiler cannot resolve the name. MSVC does not implement two-phase name lookup and delays the lookup until the second phase.
The full specialization
template<>
struct Event<void, void> : public EventBase<Event<> >
{
void trigger() const
{
// No error here even without `this`, for some reason!
auto it = subscribers.cbegin();
}
};
does not suffer from this problem, because both the class and its base are regular classes, not class templates, and there is no template dependency to begin with.
When porting C++ code from MSVC to gcc/Clang, dependent name lookup disambiguation and the template keyword disambiguation (i.e. calling member function template using ::template, ->template or .template syntax) are two of the subtleties that you have to deal with (empty base optimization is another one). For all the Standards compliance rhetoric, this will probably never be fixed for reasons of backwards compatibility.