I have played around with this for a while, and this is what I see
(the delegations in the constructors have been removed to eliminate white noise):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
#include <iostream>
#include <typeinfo>
template <class T> class not_null
{
public:
template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>>
constexpr not_null(U&&) : ptr_(nullptr)
{ std::cout << "universal constructor: U == " << typeid(U).name() << '\n' ; }
template <typename U, typename Dummy = std::enable_if_t<std::is_convertible<U, T>::value>>
constexpr not_null(const not_null<U>&) : ptr_(nullptr)
{
std::cout << "type-converting constructor: not_null<U> == "
<< typeid( not_null<U> ).name() << ">\n" ;
}
// not_null(const not_null&) : ptr_(nullptr) // ptr_(other.get())
// { std::cout << "copy constructor\n" ; }
#ifdef OPERATOR_T
constexpr operator T() const { std::cout << "converting...\n"; return get(); }
#endif // OPERATOR_T
constexpr T get() const { return ptr_ ; }
private: T ptr_;
};
struct base { virtual ~base() = default ; };
struct derived : base {};
int main()
{
std::cout << "not_null<derived*> pd { new derived } : " ;
not_null<derived*> pd { new derived } ;
std::cout << " not_null<base*> pb2 {pd} : " ;
not_null<base*> pb2 {pd} ;
}
|
LLVM:
=============== LLVM ========================
without operator T()
not_null<derived*> pd { new derived } : universal constructor: U == P7derived
not_null<base*> pb2 {pd} : type-converting constructor: not_null<U> == 8not_nullIP7derivedE>
with operator T()
not_null<derived*> pd { new derived } : universal constructor: U == P7derived
not_null<base*> pb2 {pd} : universal constructor: U == 8not_nullIP7derivedE |
GNU:
=============== GNU ========================
without operator T()
not_null<derived*> pd { new derived } : universal constructor: U == P7derived
not_null<base*> pb2 {pd} : type-converting constructor: not_null<U> == 8not_nullIP7derivedE>
with operator T()
not_null<derived*> pd { new derived } : universal constructor: U == P7derived
not_null<base*> pb2 {pd} : universal constructor: U == 8not_nullIP7derivedE |
http://coliru.stacked-crooked.com/a/08057d1838faba2b
Microsoft:
without operator T()
not_null<derived*> pd { new derived } : universal constructor: U == struct derived * __ptr64
not_null<base*> pb2 {pd} : type-converting constructor: not_null<U> == class not_null<struct derived * __ptr64>>
with operator T()
not_null<derived*> pd { new derived } : universal constructor: U == struct derived * __ptr64
not_null<base*> pb2 {pd} : universal constructor: U == class not_null<struct derived * __ptr64> |
http://rextester.com/LIJFJ46520
http://rextester.com/JXN99671
All three mainstream compilers behave in an identical manner; with an
accessible operator T() present, the resolution of the overload gives a different result, even though the operator plays no part in the eventual call.
a. Is this the correct behaviour as specified by the standard?
(It is likely that it is, since all three compilers interpret it in the same way)
If the answer to a. is yes:
b. What part does the presence or absence of an accessible operator T() play in resolving the overload?
c. If it is involved in overload resolution, why is it not called?
d. If the standard does specify that this is the expected behaviour, is there a defect in the standard?
I have some vague, hough somewhat strong, hunches; but I do not have enough knowledge to be able to give a definitive answer.
I'm sending a PM to Cubbi requesting him to have a look at this, and enlighten us.