Why don't types with invalid inheritance get rejected when passed as template parameters?
Asked Answered
K

3

8

As we all know, classes can't be inherited from fundamental types and from classes that are marked as final. But despite that, the code presented below compiles without any problems on Clang 12 and GCC 9.

#include <type_traits>

template<typename T>
struct Inheriter : public T{};

int main()
{
    std::void_t<Inheriter<int>>();
}
Kilocalorie answered 4/4, 2022 at 12:43 Comment(6)
Substitution failure is not an errorAcervate
Note that Inheriter<int> i; would fail with error: base specifier must name a class as you expect godbolt.org/z/516qWc58PDrainpipe
is there a particular reason you used void_t ? I mean if you use a template that requires an "ok" type as argument you get the expected error, eg std::vector<Inheriter<int>>. In other words: is the question about void_t or about Inheriter<int> specifically? You seem to mix two things which could actually make 2 seperate questionsExclude
@Acervate substitution failure is not an error whenever there are other substitution candidates, but that's not a case here.Kilocalorie
@463035818_is_not_a_number this question is about void_t not discarding template parameters which are ill-formedKilocalorie
interesting question. I cannot point to what causes this, but consider that you can do the same with template <typename T> struct foo {}; as it doesnt actually need to instantiate whatever T is (similar to void_t)Exclude
B
5

There will only be an error due to the inheritance if the template specialization Inheriter<int> is instantiated.

Simply using the specialization, e.g. as a template argument, does not cause implicit instantiation. Roughly speaking implicit instantiation of the class template specialization happens only if it is used in a context that requires the class to be complete or otherwise depends on class completeness.

std::void_t is defined as

template<typename...>
using void_t = void;

There is nothing in this alias template that requires the template argument to be a complete type. As a consequence no implicit instantiation will happen.

The program is therefore well-formed and the compiler should not reject it.

Buoyage answered 4/4, 2022 at 23:44 Comment(2)
in other words, if it's not instantiated, it did not happen ;) - well. almost true... syntax is checked.Arthurarthurian
Instantiation is the keyword. You`d better highlight it if SO had provided the means.Whangee
R
6

Per [expr.type.conv]:

If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of type void that performs no initialization

Meaning T() when T is void (as in your case) is a no-op, and void_t is defined as (per [temp.alias])

template<typename...> using void_t = void;

Meaning that if a valid type is supplied, then it will always become void.

Here's my take:

The code is syntactically correct, and if type substitution succeeds it is provably a no-op. Per the as-if rule, the compiler can prove that the program without this line altogether has identical runtime behavior, and therefore it is safe to ignore it completely. I can reproduce this behavior in MSVC, GCC, and Clang.

Richly answered 4/4, 2022 at 13:8 Comment(0)
B
5

There will only be an error due to the inheritance if the template specialization Inheriter<int> is instantiated.

Simply using the specialization, e.g. as a template argument, does not cause implicit instantiation. Roughly speaking implicit instantiation of the class template specialization happens only if it is used in a context that requires the class to be complete or otherwise depends on class completeness.

std::void_t is defined as

template<typename...>
using void_t = void;

There is nothing in this alias template that requires the template argument to be a complete type. As a consequence no implicit instantiation will happen.

The program is therefore well-formed and the compiler should not reject it.

Buoyage answered 4/4, 2022 at 23:44 Comment(2)
in other words, if it's not instantiated, it did not happen ;) - well. almost true... syntax is checked.Arthurarthurian
Instantiation is the keyword. You`d better highlight it if SO had provided the means.Whangee
T
1

The compiler requires that the code be free of syntactic errors. And the sample snippet doesn't have any. Only when you create an Inheriter object can the compiler raise any errors (such as the ones you are expecting)

Train answered 4/4, 2022 at 12:55 Comment(2)
Well, if I write std::void_t<decltype(Inheriter<int>{})>(); there wouldn't be any syntactic errors as well, but that led to the compilation errorKilocalorie
@icant, yes, but notice that std::void_t<decltype(std::declval<Inheriter<int>>())>(); doesn't led to any errors.Vociferance

© 2022 - 2024 — McMap. All rights reserved.