Constructor instantiation in a world of guaranteed copy elision - c++

Consider this example:
template <typename T>
using type = typename T::type;
template <typename T>
struct A
{
A(type<T>);
};
A<int> f();
A<int> g() { return f(); }
Neither gcc nor clang compile this code due to int not having a nested type typedef. But why is that constructor being instantiated at all? f() is a prvalue of the same type as the return of g(), there shouldn't even be a move there. What is causing us to instantiate the bad constructor?

The constructor is a bit of a red herring. The same would happen if it was any other member function.
template <typename T>
struct A
{
void foo(type<T>); // Same error
};
This is on account of [temp.inst]/2
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions, default arguments, or noexcept-specifiers of the class
member functions, [...]
The declaration is instantiated, so type<T> has to be well-formed.

Related

Why cant the c++ template compiler infer types from covariant smart pointer arguments?

I have the following code
https://godbolt.org/z/7d1arK
#include <memory>
template <typename T>
class A {
};
template <typename T>
class B : public A<T> {};
template <typename T>
void Foo(std::shared_ptr<A<T>> arg){}
int main(){
auto bptr = std::make_shared<B<int>>();
std::shared_ptr<A<int>> aptr = bptr;
// This compiles
Foo(aptr);
// This does not compile
Foo(bptr);
// This compiles
Foo<int>(bptr);
}
My question is why can't the compiler handle the line?
Foo(bptr);
Template argument deduction and user-defined conversions do not mix. Because they are related by a user-defined conversion, shared_ptr<A<int>> and shared_ptr<B<int>> are not "covariant" in the same sense as types like A<int>* and B<int>* for purposes of the C++ language.
Though that's enough to make the call illegal, this case is even harder because there's not a simple user-defined converting constructor or conversion function involved here, but a constructor template:
template <class T> class std::shared_ptr {
public:
template <class Y>
shared_ptr(const shared_ptr<Y>&);
// ...
};
So here the compiler would need to deduce both T and Y to determine T. "Obviously" Y will deduce to int, but the general case of this complication is not practical to solve.
And actually in this case, int is not the only possible value of T. The call Foo<const int>(bptr) would also be legal.
The C++ language specifies exactly when template argument deduction will and won't succeed, and with what resulting template arguments, so that we can be certain code working on one conforming compiler won't fail on another, at least not for this reason. At some point the line must be drawn, and one such line is "user-defined conversions are not considered when deducing template arguments".
As #SamVarshavchik and #aschepler mentioned, template deduction takes place without considering user-defined conversions, which would be necessary to convert from std::shared_ptr<B<int>> to std::shared_ptr<A<int>>. Therefore no suitable instantiation of the template is found and the call fails.
One way to enable this would be to use a wider range of templates, with a static assertion to narrow it down to enabling only actual derived classes of A:
#include <memory>
#include <type_traits>
template <typename T>
class A { };
template <typename T>
class B : public A<T> { };
class C { };
template <template<class> class Derived, class T>
void Foo(std::shared_ptr<Derived<T>> arg) {
static_assert(std::is_base_of_v<A<T>, Derived<T>>);
}
int main() {
auto bptr = std::make_shared<B<int>>();
std::shared_ptr<A<int>> aptr = bptr;
auto cptr = std::make_shared<C>();
// This compiles
Foo(aptr);
// This compiles
Foo(bptr);
// This does not compile
Foo(cptr);
}
In this case, Foo will be able to accept as an argument a std::shared_ptr to anything that inherits from A.
Like #SamVarshavchik said, user-defined implicit conversions are not allowed in template argument deduction. bptr is of type std::shared_ptr<B<int> > which is implicitly convertible to std::shared_ptr<A<int> > since B<int>* is implicitly convertible to A<int>*, but the template expects something like std::shared_ptr<A<T> > directly.

function template with unused template parameter

template<typename T>
struct a
{
using type = int;
typename T::type i;
};
template<typename T, typename = a<T>>
void f1(T) {}
template<typename T, typename = typename a<T>::type>
void f2(T) {}
int main()
{
f1<int>(1); // ok
f2<int>(1); // error
return 0;
}
An instantiation of a<int> should be an error because int::type is illegal. But it seems that f1<int> can't cause the instantiation of a<T>, but f2<int> can. What's the reason?
When type is used as the template argument (including default template argument), it's not required to be complete type.
A template argument for a type template parameter must be a type-id, which may name an incomplete type:
So for f1, the default template argument is a<T> and it doesn't have to be complete. Given f1<int>(1); a<int> doesn't need to be instantiated.
But when you refer to the member of the class template, as the default template argument typename a<T>::type of f2, a<T> has to be complete type and then cause implicit instantiation.
When code refers to a template in context that requires a completely defined type, or when the completeness of the type affects the code, and this particular type has not been explicitly instantiated, implicit instantiation occurs. For example, when an object of this type is constructed, but not when a pointer to this type is constructed.
This applies to the members of the class template: unless the member is used in the program, it is not instantiated, and does not require a definition.
So given f2<int>(1);, a<int> will be instantiated and then cause the compilation error.

Excluding conversion operator from class template ...<typename T> based on traits on T

This question covers C++03, and how to omit a conversion operator from a class template, say template<typename T> struct Foo { ... };, given traits on T.
(Questions at the bottom)
Background
Ideally, I'd would like to make use of an enable_if construct and SFINAE to exclude a conversion operator based on traits of T of class template (see Foo above), but default template arguments may not be used in function templates in C++03, which (afaik) excludes that approach; as previously covered in the following thread:
enable_if and conversion operator?
Instead, I'm using an approach where the type of the return value of the conversion operator is conditional on traits on T, particularly being a dummy (void or some private externally inaccessible type) for certain types of T. This approach seems to work fine, but I'm uncertain what possible pitfalls I might be digging for myself; specifically what is guaranteed by the (C++03) Standard in this context.
Consider the following example (which I've tried to keep as minimal as possible), using the approach described in the previous paragraph:
include/util.h:
namespace util {
// dummy predicate: is T int?
template <typename T> struct is_int { static const bool value = false; };
template <> struct is_int<int> { static const bool value = true; };
template <typename T> const bool is_int<T>::value;
// [meta.trans.other]/conditional
template <bool B, class T, class F> struct conditional { typedef T type; };
template <class T, class F> struct conditional<false, T, F> { typedef F type; };
// class template with non-template operator() member
template <typename T> struct Foo {
explicit Foo(const T &value) : value_(value) {}
// [Question regarding this conversion operator here]
operator typename conditional<is_int<T>::value, int, void>::type() const {
return value_;
}
private:
T value_;
};
/* Alternatively */
template <typename T> class Bar {
struct Dummy {};
T value_;
public:
explicit Bar(const T &value) : value_(value) {}
operator typename conditional<is_int<T>::value, int, Dummy>::type() const {
return value_;
}
};
} // namespace util
main.cc:
#include "include/util.h"
void baz(int) {}
int main()
{
const util::Foo<int> foo_int(42);
baz(foo_int); // OK
const util::Foo<char> foo_char('a');
const util::Bar<int> bar_int(42);
baz(bar_int); // OK
const util::Bar<char> bar_char('a');
/* OK, expected:
Error: cannot convert ‘const util::Foo<char>’/‘const util::Bar<char>’
to ‘int’ for argument ‘1’ to ‘void baz(int)
baz(foo_char);
baz(bar_char); */
return 0;
}
This compiles fine using gcc and clang (-std=c++03), but I'm wondering if it's really OK to conditionally have an invalid body(/return) for the conversion operator, as is the case e.g. for a full instantiation of Foo<char>. I'm assuming it's fine due to partial implicit instantiation; [temp.inst]/1 (14.7.1 in the C++03 standard draft) describes [emphasis mine]:
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions or default arguments, of the class member functions,
member classes, static data members and member templates; and it
causes the implicit instantiation of the definitions of member
anonymous unions. Unless a member of a class template or a member
template has been explicitly instantiated or explicitly specialized,
the specialization of the member is implicitly instantiated when the
specialization is referenced in a context that requires the member
definition to exist;
Question
Does the C++03 Standard guarantee that a conditionally (trait on T) invalid non-template member function body of a class template is not an error in case it is not referenced from instantiations of the class where it is/would be invalid?

decltype(auto) in member function ignores invalid body, decltype(expr) fails

I have a simple templated wrapper struct with a member function calling .error() on an object of its template type.
template <typename T>
struct Wrapper {
T t;
decltype(auto) f() {
return t.error(); // calls .error()
}
};
If I instantiate this with a type that doesn't have an error() member function, it's fine as long as I don't call it. This is the behavior I want.
Wrapper<int> w; // no problem here
// w.error(); // uncommented causes compilation failure
If I use what I thought was the semantic equivalent with a trailing return type, it errors on the variable declaration
template <typename T>
struct Wrapper {
T t;
auto f() -> decltype(t.error()) {
return t.error();
}
};
Wrapper<int> w; // error here
I'll accept that the two are not semantically equivalent, but is there anyway to get the behavior of the former using a trailing return type (C++11 only) without specializing the whole class with some kind of HasError tmp trickery?
The difference between the versions
decltype(auto) f();
auto f() -> decltype(t.error());
is that the function declaration of the second can be invalid. Return type deduction for function templates happens when the definition is instantiated [dcl.spec.auto]/12. Although I could not find anything about return type deduction for member functions of class templates, I think they behave similarly.
Implicitly instantiating the class template Wrapper leads to the instantiation of the declarations, but not of the definitions of all (non-virtual) member functions [temp.inst]/1. The declaration decltype(auto) f(); has a non-deduced placeholder but is valid. On the other hand, auto f() -> decltype(t.error()); has an invalid return type for some instantiations.
A simple solution in C++11 is to postpone the determination of the return type, e.g. by turning f into a function template:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() );
The definition of that function worries me a bit, though:
template<typename U = T>
auto f() -> decltype( std::declval<U&>().error() )
{
return t.error();
}
For the specializations of Wrapper where t.error() is not valid, the above f is a function template that cannot produce valid specializations. This could fall under [temp.res]/8, which says that such templates are ill-formed, No Diagnostic Required:
If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic
required.
However, I suspect that rule has been introduced to allow, but not require, implementations to check for errors in non-instantiated templates. In this case, there is no programming error in the source code; the error would occur in instantiations of the class template described by the source code. Therefore, I think it should be fine.
An alternative solution is to use a fall-back return type to make the function declaration well-formed even if the definition is not (for all instantiations):
#include <type_traits>
template<typename T> struct type_is { using type = T; };
template <typename T>
struct Wrapper {
T t;
template<typename U=T, typename=void>
struct error_return_type_or_void : type_is<void> {};
template<typename U>
struct error_return_type_or_void
<U, decltype(std::declval<U&>().error(), void())>
: type_is<decltype(std::declval<U&>().error())> {};
auto f() -> typename error_return_type_or_void<>::type {
return t.error();
}
};
One approach is a tagged crtp.
// todo:
template<class T>
struct has_error; // true_type if T.error() is valid
template<class D,class T,bool Test=has_error<T>{}>
struct do_whatever {
D* self(){return static_cast<D*>(this);}
D const* self()const{return static_cast<D const*>(this);}
auto f()->decltype(self()->t.error()) {
return self()->t.error();
}
};
template<class D,class T>
struct do_whatever<D,T,false>{};
now f() just isn't there if T has no error().

Member class instantiation

N4296::14.7.1/1 [temp.inst] told us the following:
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions, [...], member classes, [...]
What is that rule about? Let me give you an example:
template<class T>
class A
{
public:
template<class W> class Y; //1, declaration
template<class V> class U{ V v; }; //2, definition
};
A<int> a; //3, implicit instantiation
int main(){ }
Does implicit instantiation at //3 cause implicit instantiation at //2 and at //1? If so, what template argument was used to instantiate those member classes?
There is nothing special about those member templates, compared to the "outer" template. The compiler reads them as a declaration, such that it knows that there exist names as A<T>::Y<W> and A<T>::U<V>, much the same as when you declare the template for class A:
template <typename T>
class A {
int a;
};
Which also only declares the existence of a class A<T> but does not instantiate it.
Instantiation is deferred until a templated type is actually used (or explicitly instantiated), which equally applies to the member templates.