34

The code below compiles in Visual Studio 2013, gcc 4.8, clang 3.4 and clang 3.5 (Apple LLVM 6.0) but does not compile in clang 3.6 (via Apple LLVM 6.1)

The code is a simplified version of a complicated class in our codebase which is the minimum required to exhibit the issue.

The crux of the problem is that the copy construction of TYPED_VALUE is, in 3.6, evaluating the templated conversion operator for type STRING because of the presence of a constructor that accepts a STRING; this causes std::is_constructible to be evaluated which leads to it needing the definition of STRING (which we cannot provide here - would lead to a circular dependency in the full code).

class STRING;

class TYPED_VALUE
{
public:
    TYPED_VALUE( const TYPED_VALUE& ) = default; // explicit or implicit doesn't make a difference
    TYPED_VALUE( const STRING & ) {}

    template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
    operator TYPE( void ) const = delete;
};

class TYPED_STORAGE
{
public:
    TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}

    TYPED_VALUE value;
};

The error message is

/type_traits:2329:38: error: incomplete type 'SICORE::STRING' used in type trait expression
    : public integral_constant<bool, __is_constructible(_Tp, _Args...)>
                                     ^
/main.cpp:348:99: note: in instantiation of template class 'std::__1::is_constructible<SICORE::STRING, const SICORE::STRING &>' requested here
        template< typename TYPE, typename std::enable_if<!std::is_pointer< TYPE >::value && !std::is_constructible< TYPE, const STRING& >::value && !std::is_constructible< TYPE, bool >::value, int >::type = 0 >
                                                                                                  ^
/main.cpp:349:9: note: while substituting prior template arguments into non-type template parameter [with TYPE = SICORE::STRING]
        operator TYPE( void ) const = delete;
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/main.cpp:355:56: note: while substituting deduced template arguments into function template 'operator type-parameter-0-0' [with TYPE = SICORE::STRING, $1 = (no value)]
        TYPED_STORAGE( const TYPED_VALUE &v ) : value( v ) {}
                                                       ^
/main.cpp:340:11: note: forward declaration of 'SICORE::STRING'
    class STRING;
          ^

To me this seems like a bug in 3.6, in previous versions the overload resolution determines that the copy constructor is the best fit without having to evaluate the template arguments - I tried to understand the overload resolution notes in the standard but I think that just confused me more ;)

(This can be fixed by making either the constructor or the conversion operator explicit I realise, but that is not the behaviour we want)

Any standard experts out there know the answer?

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
Ed Lambert
  • 341
  • 3
  • 4
  • 3
    This might fall under [temp.inst]/7 "If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place." – dyp Apr 14 '15 at 00:10
  • 3
    To expand on @dyp's comment, in general overload resolution works in three steps: 1) enumerate all possible candidate functions; 2) determine the conversion sequences required for each argument for each candidate (and remove non-viable candidates); 3) compare the conversion sequences for the best match. If we follow this procedure to the letter, then that `is_constructible` will *always* be instantiated, but the standard gives leeway to the compiler to not instantiate it if it can determine the correct function to call. – T.C. Apr 14 '15 at 00:42
  • Ok thanks guys, I'll have a think how I can achieve the desired effect in a different way! – Ed Lambert Apr 16 '15 at 23:10
  • If anyone is interested I just starting trying to use the Visual Studio 2015 RC to compile our codebase and it to trips up on the same bit of code. – Ed Lambert May 01 '15 at 23:28
  • please gives us a self-contained example of your code (including `main()`) that can be pasted into an online compiler – TemplateRex Jun 23 '15 at 09:07
  • 2
    @TemplateRex Ummm just add a `#include ` and an empty `main`. All the relevant code is already in the OP. – dyp Jun 23 '15 at 09:59
  • @dyp tnx, I missed that. BTW, the `std::is_constructible< TYPE, bool >::value` is already giving the error, not just the `std::is_constructible< TYPE, const STRING& >::value` – TemplateRex Jun 23 '15 at 11:06
  • @TemplateRex yes, it is the it is become the TYPE is coming from the SICORE::STRING constructor. for TYPED_VALUE – Ed Lambert Jun 24 '15 at 12:15
  • I was just looking to how I can replace this code and I am at a bit of a loss. The purpose of this code is to stop C style casts to unsupported types (it has a another template coercion operator based on some template magic - if the appropriate header file is included for that type when needed) The problem we had is people were sometimes writing code like this NEW_TYPE type = (const NEW_TYPE&)typed_value; Which compiled without including the appropriate header file but in that situation just returned the address of typed_value cast to NEW_TYPE which then crashed. – Ed Lambert Jun 24 '15 at 12:16

3 Answers3

1

I believe Clang is correct to produce this error:

The [temp.inst] section of the C++ standard in paragraph 10 says:

If a function template or a member function template specialization is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (14.8.3).

Forming the implicit conversion sequence necessary to rank the overload candidates for the call to TYPE_VALUE's constructor requires the instantiation of the conversion operator. And the use of an incomplete type parameter to the trait doesn't form an invalid type, so this isn't a substitution failure, it is a hard error.

Chandler Carruth
  • 3,011
  • 1
  • 18
  • 26
0

The copy constructor of TYPED_VALUE uses a reference to STRING, it should not be evaluated.
I think this is a clang error.
I haven't read the new c++ standard for a long time, however, I couldn't make sure it hadn't changed.

Alan
  • 143
  • 1
  • 6
-1

templates are instantiated as-needed, and I think Clang 3.6 implemented a DR where it needed to instantiate a template earlier than 3.5 did.