Is it valid to use an identifier of a class, that is only declared but not defined, as template argument and for template specialization.
Something like that:
template<typename T>
class NodeInfo;
template<typename T>
class GraphInfo;
template<typename T>
class Graph {
public:
GraphInfo<T> graphInfo;
NodeInfo<T> nodeInfo;
};
// specialisation
class ContextInfo;
template <>
class NodeInfo<ContextInfo> {
public:
int a, b, c;
};
template <>
class GraphInfo<ContextInfo> {
public:
int a, b, c;
};
int main() {
Graph<ContextInfo> g;
}
This compiles fine without any warning in gcc 7, but I'm wondering if this is a valid thing to do or do I create some kind of undefined behavior with that?
This is fine in principle:
[ Note: A template type argument may be an incomplete type (6.9). — end note ]
However, incomplete class types may not be used with the standard library, unless otherwise specified:
[The] effects are undefined in the following cases: [...]
— if an incomplete type (6.9) is used as a template argument when instantiating a template component,
unless specifically allowed for that component.
For example:
The template parameter T of declval may be an incomplete type.
Since C++17, more library facilities permit the use of incomplete types; for example vector:
An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness
requirements. T shall be complete before any member of the resulting specialization of
vector is referenced.
Yes it's pretty fine. An incomplete type is allowed to used as template argument.
A template argument for a type template parameter must be a type-id, which may name an incomplete type
Related
I want a setup like the following:
template <typename T> class a {};
class b : public a<int> {};
template <typename T>
void do_foo(std::unique_ptr<a<T>> foo)
{
// Do something with foo
}
int main()
{
do_foo(std::make_unique<b>());
}
This fails to compile with a note saying template argument deduction/substitution failed and mismatched types 'a<T>' and 'b'. It's pretty self-explanatory. I can help the compiler along by writing do_foo<int>(std::make_unique<b>());, but then I'm repeating myself by writing int twice.
Is there a way to get the compiler to deduce the template parameter in this case? And what would you call this behaviour? I tried searching for things like "template type deduction for inherited type", "polymorphic template deduction" etc.
Is there a way to get the compiler to deduce the template parameter in this case?
No. Not in C++14 (or even C++20).
And what would you call this behaviour?
Standard compliant. To be specific, this paragraph applies:
[temp.deduct.call]
4 In general, the deduction process attempts to find template
argument values that will make the deduced A identical to A (after
the type A is transformed as described above). However, there are
three cases that allow a difference:
If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the
transformed A.
The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification
conversion ([conv.qual]).
If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A.
Likewise, if P is a pointer to a class of the form
simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.
This is an exhaustive list of cases where a template argument can be validly deduced from a function argument even if it doesn't match the pattern of the function parameter exactly. The first and second bullets deal with things like
template<class A1> void func(A1&){}
template<class A2> void func(A2*){}
int main() {
const int i = 1;
func(i); // A1 = const int
func(&i); // A2 = const int
}
The third bullet is the one that is closest to our case. A class derived from a template specialization can be used to deduce a template parameter pertaining to its base. Why doesn't it work in your case? Because the function template parameter is unique_ptr<a<T>> and the argument you call it with is unique_ptr<b>. The unique_ptr specializations are not themselves related by inheritance. So they don't match the bullet, and deduction fails.
But it doesn't mean that a wrapper like unique_ptr prevents template argument deduction entirely. For instance:
template <typename> struct A {};
struct B : A<int> {};
template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};
template<typename T>
void do_smth(wrapper<A<T>>) {}
int main() {
do_smth(wrapper<B>{});
}
In this case, wrapper<B> derives from wrapper<A<int>>. So the third bullet is applicable. And by the complex (and recursive) process of template argument deduction, it allows B to match A<T> and deduce T = int.
TL;DR: unique_ptr<T> specializations cannot replicate the behavior of raw pointers. They don't inherit from the specializations of unique_ptr over T's bases. Maybe if reflection ever comes to C++, we'll be able to meta-program a smart pointer that does behave that way.
As workaround, you might add overload:
template <typename T>
void do_foo_impl(a<T>* foo)
{
return do_foo(std::unique_ptr<a<T>>(foo));
}
template <typename T>
auto do_foo(std::unique_ptr<T> foo) -> decltype(do_foo_impl(foo.release()))
{
do_foo_impl(foo.release());
}
Demo
This code fails to compile because of the inheritance from an incomplete type (https://godbolt.org/z/G35wj9):
template<typename>
class Incomplete;
class T : public Incomplete<T> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T x;
}
I believed that this rule also applied to class templates. However, this code compiles fine (https://godbolt.org/z/cU6GNt):
template<typename>
class Incomplete;
template<int d>
class T : public Incomplete<T<d>> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T<1> x;
}
When class templates are involved, is the base class only required to be complete at the point of instantiation?
When class templates are involved, is the base class only required to be complete at the point of instantiation?
If it's a dependent base then yes. By that virtue a compiler has no idea what Incomplete<T<d>> is at the point of template definition. After all, for some values of d we can have ourselves a specialization of Incomplete<T<d>> that is entirely different from the primary template declaration.
template<>
class Incomplete<T<0>> {};
This is not a circular dependency. Simply naming the specialization T<0> does not cause it to be instantiated. It's just a type name. But it does mean the compiler has no recourse but wait until it can check the base class is valid.
On the other hand, if the base class is not a dependent type, then using it as base would be ill-formed.
The standard covers this in [temp.inst] §1 (taken from C++17) :
Unless a class template specialization has been explicitly instantiated (17.7.2) or explicitly specialized (17.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. [ Note: In particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and a conversion between pointers to class type depends on the inheritance relationship between the two classes involved. — end note ] [ Example:
template<class T> class B { /* ... */ };
template<class T> class D : public B<T> { /* ... */ };
void f(void*);
void f(B<int>*);
void g(D<int>* p, D<char>* pp, D<double>* ppp) {
f(p); // instantiation of D<int> required: call f(B<int>*)
B<char>* q = pp; // instantiation of D<char> required: convert D<char>* to B<char>*
delete ppp; // instantiation of D<double> required
}
— end example ] If a class template has been declared, but not defined, at the point of instantiation (17.6.4.1), the instantiation yields an incomplete class type (6.9). [ Example:
template<class T> class X;
X<char> ch; // error: incomplete type X<char>
— end example ] [ Note: Within a template declaration, a local class (12.4) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, noexcept-specifiers, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. — end note ]
Also covered on cppreference.
Just keep in mind that a class template is not a type, and no code is generated for it. Code is generated when the template is instantiated. When a class template is instantiated (implicitly or explicitly), an actual class (type) is generated (including the code for it).
The only difference is, that in your example you use the Incomplete class inside of a template.
In the original example it was used by a class. The type of the class will be created by the compiler at this time, thus before Incomplete is defined.
In your code, Incomplete is used in another template T, (roughly speaking: T is a mold to generate various types by use of Incomplete). At this time the compiler does nothing, it just stores a "rule to generate types" (what I called the mold).
The compiler checks validity of T at the time it is used, hence at the line - then the actual type T<1> : public Incomplete gets generated.
T (the mold) is "valid" iff Incomplete is defined
[[maybe_unused]] T<1> x;
At this point the template class Incomplete is well defined, and the compiler is happy.
It is possible to copy or construct a shared_ptr<Base> from shared_ptr<Deriver> (i.e. shared_ptr<Base> ptr = make_shared<Derived>()). But as we all know, template classes are not convertible to each other, even if the template arguments are. So how can shared_ptrs check if the value of their pointers are convertible and do the conversion if they are?
Yes, specializations of the same class template by default have almost no relationship and are essentially treated like unrelated types. But you can always define implicit conversions between class types by defining converting constructors (To::To(const From&)) and/or conversion functions (From::operator To() const).
So what std::shared_ptr does is define template converting constructors:
namespace std {
template <class T>
class shared_ptr {
public:
template <class Y>
shared_ptr(const shared_ptr<Y>&);
template <class Y>
shared_ptr(shared_ptr<Y>&&);
// ...
};
}
Though the declaration as shown would allow conversions from any shared_ptr to any other, not just when the template argument types are compatible. But the Standard also says about these constructors ([util.smartptr]/5 and [util.smartptr.const]/18 and util.smartptr.const]/21):
For the purposes of subclause [util.smartptr], a pointer type Y* is said to be compatible with a pointer type T* when either Y* is convertible to T* or Y is U[N] and T is cv U[].
The [...] constructor shall not participate in overload resolution unless Y* is compatible with T*.
Although this restriction could be done in any way, including compiler-specific features, most implementations will enforce the restriction using an SFINAE technique (Substitution Failure Is Not An Error). One possible implementation:
#include <cstddef>
#include <type_traits>
namespace std {
template <class Y, class T>
struct __smartptr_compatible
: is_convertible<Y*, T*> {};
template <class U, class V, size_t N>
struct __smartptr_compatible<U[N], V[]>
: bool_constant<is_same_v<remove_cv_t<U>, remove_cv_t<V>> &&
is_convertible_v<U*, V*>> {};
template <class T>
class shared_ptr {
public:
template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
shared_ptr(const shared_ptr<Y>&);
template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
shared_ptr(shared_ptr<Y>&&);
// ...
};
}
Here the helper template __smartptr_compatible<Y, T> acts as a "trait": it has a static constexpr member value which is true when the types are compatible as defined, or false otherwise. Then std::enable_if is a trait which has a member type called type when its first template argument is true, or does not have a member named type when its first template argument is false, making the type alias std::enable_if_t invalid.
So if template type deduction for either constructor deduces the type Y so that Y* is not compatible with T*, substituting that Y into the enable_if_t default template argument is invalid. Since that happens while substituting a deduced template argument, the effect is just to remove the entire function template from consideration for overload resolution. Sometimes an SFINAE technique is used to force selecting a different overload instead, or as here (most of the time), it can just make the user's code fail to compile. Though in the case of the compile error, it will help that a message appears somewhere in the output that the template was invalid, rather than some error even deeper within internal template code. (Also, an SFINAE setup like this makes it possible for a different template to use its own SFINAE technique to test whether or not a certain template specialization, type-dependent expression, etc. is or isn't valid.)
It works because shared_ptr has (among others) a templated constructor
template<typename U> shared_ptr(U * ptr);
If U* is not convertible to the shared_ptr's contained type then you will get an error buried somewhere in the shared_ptr implementation.
This code:
template <class T>
class Foo {};
typedef Foo<void*> Bar;
template <class T>
class Foo<T*> : public Bar {};
// use Foo<int*> somewhere.
Compiles and works fine in MSVC 9.0, but doesn't compile in GCC 4.1.1 or GCC 4.3.4, with the error:
error: invalid use of undefined type 'class Bar'
Is this illegal C++ that MSVC accepts incorrectly, or a limitation of GCC?
Either way, how can I work around this get the desired behaviour: pointer specialisations of Foo that inherit from unspecialised Foo<void*>?
You cannot do that, except by writing the specialization for all T*, except when T is void. Otherwise, you will derive the class from itself, which for obvious reasons can't work.
Instantiating the primary class template for arguments that have an explicit or partial specialization is not possible. If you try to, by provoking an instantiation before the explicit or partial specialization is visible (note that your code did not provoke such an instantiation), your program is ill-formed, with no diagnostic being required (which effectively renders the behavior undefined).
To achieve the above work-around, you can use SFINAE
template <class T>
struct isnt_void { typedef void type; };
template<> struct isnt_void<void> { };
template <class T, class = void>
class Foo {};
template <class T>
class Foo<T*, typename isnt_void<T>::type> : public Foo<void*> {};
The typedef is a red herring.
The following code is equivalent:
template <class T>
class Foo {};
template <class T>
class Foo<T*> : public Foo<void*> {};
It should be clear that, although Foo<T*> is declared at this point, it is not defined. And thus you may not use it as a base.
[class.name] (2003 wording, 9.1/2):
A class definition introduces the class name into the scope where it is defined
[class.mem] (2003 wording, 9.2/2):
A class is considered a
completely-defined object type (3.9)
(or complete type) at the closing } of
the class-specifier. Within the class
member-specification, the class is
regarded as complete within function
bodies, default arguments and
constructor ctor-initializers
(including such things in nested
classes). Otherwise it is regarded as
incomplete within its own class
member-specification.
[class.derived] (2003 wording, 10/1):
The class-name in a base-specifier shall not be an incompletely defined class (clause 9);
A superior solution would be to compose of Foo<void*>. After all, you don't want the raw void* interface cluttering up your stuff, and you don't want a Foo<T*> to be convertible to a Foo<void*>.
Alternatively, you could fully specialize Foo<void*> beforehand.
Assuming, of course, that you're doing this for type folding, instead of because you actually want inheritance.
Sorry for the funny title.
Prior to C++0x, there are restrictions in the use of function-local structs (“local types”) as template arguments. My question is essentially if similar restrictions apply to anonymous structs. Specifically, in the context of a trait class:
template <typename T>
struct trait;
template <>
struct trait<int> {
typedef int type;
};
template <typename T>
struct trait<std::basic_string<T> > {
typedef struct {
T value;
} type;
};
trait<std::string>::type foo; // Is this valid?
template <typename T>
void f() { }
f<trait<std::string>::type> >(); // Is this?
template <typename T>
void g() { f<typename trait<T>::type>(); }
g<std::string>(); // And this?
Is this valid and reliable? It compiles in recent versions of GCC and LLVM but I’m still insecure whether this is strictly valid, and whether it’s understood by VC++ and ICC.
For reference, the quote from the linked question in 14.3.1/2:
A local type, a type with no linkage,
an unnamed type or a type compounded
from any of these types shall not be
used as a template argument for a
template type parameter.
My interpretation is that the typedef struct is creating an alias to an unnamed type and that it thus can't be used as a template type parameter. Further note that additionally in C typedef struct {} Foo; is treated rather differently from struct Foo {}; giving precedent that the two forms are not equivalent (although admittedly that difference doesn't appear in C++).
Thus it would appear your first example works (since it's not using the unnamed type as a template type parameter), while the second and third examples would be technically invalid (since they do use it as a template type parameter).
Finally in closing I have to ask, is there a reason you can't name the struct instead of typedefing it?
EDIT: From 7.1.3/1:
...A typedef-name is thus a synonym for
another type. A typedef-name does not
introduce a new type the way a class
declaration (9.1) or enum declaration
does...
This strongly implies that using typedef in such a way does not introduce a type suitable for use as a template type-parameter.
In the upcoming standard that restriction is removed from the language. The standard says in
14.3.1 [temp.arg.type] /1
A template-argument for a template-parameter which is a type shall be a type-id.
And a typedef is a valid type-id. As a matter of fact the next paragraph contains such an example:
14.3.1 [temp.arg.type] /2
template <class T> class X { };
template <class T> void f(T t) { }
void f() {
typedef struct { } B;
B b;
X<B> x3;
f(b);
}
(Where I have trimmed most of the other examples) The example shows that an unnamed type can be used as a class template argument both in class templates and function templates.
A typedef declaration that defines an anonymous class and a typedef-name for that class, the typedef-name is the name of the class for linkage purposes. It is therefore legal to use that class as a template parameter if it meets the other criteria.
See 7.1.3p5 of the C++03 standard
If the typedef declaration defines an
unnamed class (or enum), the first
typedef-name declared by the decla-ration to be that class type (or enum
type) is used to denote the class type
(or enum type) for linkage purposes
only (3.5). [Example:
typedef struct { } *ps, S; // S is the class name for linkage purposes
This is 7.1.3p9 in the C++0x FDIS.
FWIW, this code compiles OK with MSVC2010 (modulo typos).
Well, that is equivalent to
template <typename T>
struct trait<std::basic_string<T> > {
struct type {
T value;
};
};
which is completely legitimate.