5

Whats wrong with this:

#include <type_traits>

struct A;

template<typename T>
struct B
{
    template<typename=std::enable_if<std::is_copy_constructible<T>::value>>
    void f1() {}
};

template<typename T>
struct C {};


// Type your code here, or load an example.
int main() {
    // Following fails
    B<A> b;
    // Could use this:
    // b.f1<C>();

    // This complies
    C<A> c;

    return 0;
}

/* This to be in or not doesn't make a difference
struct A
{};
*/

I tried this here: https://godbolt.org/z/NkL44s with different compilers:

  • x86-64 gcc 9.2: compiles
  • x86-64 gcc (trunk): fails
  • x86-64 clang 6.0.0: compiles
  • x86-64 clang 7.0.0 and later: fails
  • x64 msvc v19.22: compiles
  • x64 msvc v19.23 (tested internally): fails

So why do more recent compilers reject this? When instantiating B<A> it is not clear in which form f1 will be used or if it will be used at all. So why the compiler complains about it? Shouldn't the f1 member template function be checked only if it is really used?


Edit:
As mentioned in comments, I made an unintented mistake in above code: std::enable_if should have been std::enable_if_t, as in this corrected playground: https://godbolt.org/z/cyuB3d

This changes the picture of compilers passing this code without error:

  • gcc: fails
  • clang: fails
  • x64 msvc v19.22: compiles
  • x64 msvc v19.23 (tested internally): fails

However, the question remains: Why does a defaulted template parameter of a function that is never used lead to compilation failure?

  • 1
    It's `typename = std::enable_if_t<...>`, not `typename = std::enable_if<...>`. – L. F. Oct 10 '19 at 10:10
  • can you include an error message? Are all compilers complaining in the same way? – 463035818_is_not_an_ai Oct 10 '19 at 10:11
  • 1
    A simplified case: `template> void f1() {}`. The problem seems to be related to the time of resolution of default template arguments. Note that `T` being _incomplete type_ results in _undefined behaior_. The differences I can observe are caused by compiler checks for type completeness. – Daniel Langr Oct 10 '19 at 10:20
  • @L.F. I don't think it matters here. SFINAE is not applied and the anonymous template parameter may as well refer to the `std::enable_if` type itself. – Daniel Langr Oct 10 '19 at 10:23
  • I added a paragraph to the post responding to the correctly spotted misuse of `std::enable_if` versus `std::enable_if_t`. – Jojakim Stahl Oct 10 '19 at 11:07
  • @JojakimStahl Note that this is not a correct application of SFINAE. Read, e.g., [Why doesn't SFINAE (enable_if) work for member functions of a class template?](https://stackoverflow.com/q/30953248/580083). – Daniel Langr Oct 10 '19 at 11:11
  • @DanielLangr Note that this is not the same case as in the question you referred to, where the functions are all completely defined with instatiation of the template class. In my example, it is a template function, which IMHO the compiler can't instantiate until it is used. When using the function, I could pass a template parameter in which case the default parameter has no importance at all. – Jojakim Stahl Oct 10 '19 at 11:23
  • @JojakimStahl Sure, my comment was about a different problem. I think your problem with seemingly-unnecessary instantiation of a default template argument is not related to SFINAE at all. – Daniel Langr Oct 10 '19 at 11:25

2 Answers2

3

The reason is that std::is_constructible requires a complete type: (Table 42)

Template

template <class T>
struct is_­copy_­constructible;

Preconditions

T shall be a complete type, cv void, or an array of unknown bound.

Failing to meet a library "shall" requirement results in undefined behavior.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 3
    just like my answer you also miss to explain why the error in the method matters when one can expect that it is actually not instantiated – 463035818_is_not_an_ai Oct 10 '19 at 10:16
  • 1
    @formerlyknownas_463035818 Hmm, I'm not sure either. It seems that the default argument should not be instantiated ... – L. F. Oct 10 '19 at 10:31
  • 1
    It's weird that the whole section [temp.inst](http://eel.is/c++draft/temp.inst) mentions _default template argument_ only once — in the context of variable templates. I can't find anything in the Standard to specify when default template arguments should be instantiated. – Daniel Langr Oct 10 '19 at 10:56
  • @DanielLangr Yes, how template declarations themselves are instantiated seems underspecified. – L. F. Oct 10 '19 at 11:16
  • Anyway, I believe that in all cases, compilers *do instantiate* the default template argument. The only difference is whether they check for `T` begin complete type in `std::is_copy_constructible` type trait. For instance, this check was added into libstdc++ in May, 2019, i.e., relatively recently; see [this commit](https://github.com/gcc-mirror/gcc/commit/c12402f67d3ac10feaa92bf92f242ec25b6ea11f#diff-6547f965a8d66bf35a6388fcf404aaa3), namely [these added lines](https://github.com/gcc-mirror/gcc/commit/c12402f67d3ac10feaa92bf92f242ec25b6ea11f#diff-6547f965a8d66bf35a6388fcf404aaa3R982). – Daniel Langr Oct 10 '19 at 11:21
  • Replacing the template declaration of `f1` with `template::value>>` makes it compilable. – Jojakim Stahl Oct 10 '19 at 13:00
  • @JojakimStahl Apparently the default argument can't be instantiated if it depends on a template parameter (that's how SFINAE works). The other case is more difficult - I guess this has something to do with immediate contexts, but I can't say for sure .. – L. F. Oct 10 '19 at 13:17
2

From cppreference on is_copy_constructible<T>:

T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound. Otherwise, the behavior is undefined.

So it seems like you have just plain UB in older compiler versions while the newer ones are nice enough to tell you that A has to be a complete type.

Note that in the presence of UB, compliers are not reuqired to issue an error, but they may do it, which is a nice thing.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185