1

I'm trying to derive from a class which doesn't have a constructor from int but does from a nullptr, trying to make the constructor in derivative as generic as possible when it takes a single argument. But for some reason the correct constructor doesn't appear to be taken, even if substitution of int into template constructor results in a failure:

#include <cstddef>
#include <iostream>

struct Base
{
    Base(std::nullptr_t){}
    Base(){}
    // some other constructors, but not from int
};

struct Test : Base
{
    Test(std::nullptr_t) : Base(nullptr)
    {
        std::cerr << "Test(nullptr)\n";
    }
    template<typename T>
    Test(T v) : Base(v) {}
};

int main()
{
    Base b=0;       // works
    Test z=nullptr; // works
    Test t=0;       // compilation error
}

Why does it happen? Is it not what SFINAE is supposed to mean? And how can I fix this problem?

Ruslan
  • 18,162
  • 8
  • 67
  • 136

2 Answers2

2

The member initialization list is not part of the socalled "immediate context". Only this immediate context is guarded by SFINAE.

Add to this that SFINAE only guards substitution into the function declaration parts that do not belong to the function body (the definition parts). The member initialization list belongs to the constructor body. This body is independently instantiated from the declaration and any error here is fatal.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
0

GCC tells me

36576202.cpp: In instantiation of ‘Test::Test(T) [with T = int]’:
36576202.cpp:25:12:   required from here
36576202.cpp:18:23: error: no matching function for call to ‘Base::Base(int&)’
     Test(T v) : Base(v) {}
                       ^
36576202.cpp:18:23: note: candidates are:
36576202.cpp:7:5: note: Base::Base()
     Base(){}
     ^
36576202.cpp:7:5: note:   candidate expects 0 arguments, 1 provided
36576202.cpp:6:5: note: Base::Base(std::nullptr_t)
     Base(std::nullptr_t){}
     ^
36576202.cpp:6:5: note:   no known conversion for argument 1 from ‘int’ to ‘std::nullptr_t’
36576202.cpp:4:8: note: constexpr Base::Base(const Base&)
 struct Base
        ^
36576202.cpp:4:8: note:   no known conversion for argument 1 from ‘int’ to ‘const Base&’
36576202.cpp:4:8: note: constexpr Base::Base(Base&&)
36576202.cpp:4:8: note:   no known conversion for argument 1 from ‘int’ to ‘Base&&’

This shows that

  • Test t=0; goes via the template Test(T), with T==int. That's a valid substitution, so SFINAE isn't relevant.
  • Test(T==int) then needs Base(int), which doesn't exist. And there's no conversion from int to nullptr_t.
Toby Speight
  • 27,591
  • 48
  • 66
  • 103