With CRTP
idiom in C++11 I'm trying to check out if a derived class has a method named size
or not.
#include <iostream>
template<typename Derive> struct IsContainer {
template<typename Der> static constexpr bool has_size( decltype(&Der::size) ) { return true; }
template<typename Der> static constexpr bool has_size(...) { return false; }
static constexpr bool value = has_size<Derive>(nullptr);
};
struct Container : IsContainer<Container> {
const unsigned int size();
};
struct NonContainer : IsContainer<NonContainer> {};
int main() {
Container obj;
std::cout << obj.value <<std::endl;
}
I then tried to get rid of template <typename Der>
as following:
#include <iostream>
template<typename Derive> struct IsContainer {
static constexpr bool has_size( decltype(&Derive::size) ) { return true; }
static constexpr bool has_size(...) { return false; }
static constexpr bool value = has_size(nullptr);
};
struct Container : IsContainer<Container> {
const unsigned int size();
};
struct NonContainer : IsContainer<NonContainer> {};
int main() {
Container obj;
std::cout << obj.value <<std::endl;
}
Compilation error:
In instantiation of ‘struct IsContainer<Container>’:
error: incomplete type ‘Container’ used in nested name specifier
| static constexpr bool has_size( decltype(&Derive::size) ) { return true; }
| ^~~~~~~~
I encountered such a statement quoted from standard explaining why does compiler gives this error
3.3.2p6 Declaration point [basic.scope.pdecl]
Once a class member has been declared, its name can be searched for within the scope of the class
I still do not get that for which reason the first code snippet does work and the second not. I really would appreciate if someone explains it to me with detail and possibly give me a hint to get around the error of the second code snippet.
By removing the template-head template <typename Der>
, there can be no SFINAE for those member and so we get a hard error. SFINAE happens when during substitution of the template parameter, and without a template-head there is no substitution and hence no SFINAE. Note that in the second snippet, Derive
is a template parameter of the class template and not the member function itself.
Container
isn't complete until the closing brace. So decltype(&Derive::size)
in the member function parameter will give a hard error without the template head as no sfinae can happen unlike the first case. See the contrived example given at the end of the answer.
When you wrote IsContainer<Container>
then Derive= Container
and since Container
isn't complete at this point we can't write decltype(&Derive::size)
which basically is decltype(&Container::size)
in the parameter declaration. Note also that declarations of member functions are instantiated with the class template. And since in the second snippet, you don't have a template-head i.e., Derive
is a template parameter of the class not the member function, we don't get SFINAE and instead get a hard error.
Below is a convtrived example to illustrate the same. In particular, for the following program we will get a hard error as no SFINAE happens for func
.
struct C;
template<typename T> struct D
{
void func(decltype(sizeof(T::type))); //NO SFINAE here so we will get a hard error
void func();
};
int main()
{
D<C> d;
}
The error you get for the above program says:
<source>: In instantiation of 'struct D<C>':
<source>:11:11: required from here
11 | D<C> d;
| ^
<source>:7:34: error: incomplete type 'C' used in nested name specifier
7 | void func(decltype(sizeof(T::type))); //NO SFINAE here so we will get a hard error
|
Now, let's look at another example where SFINAE happens and we don't get a hard error:
struct C;
template<typename T> struct D
{
template<typename U> void bar(decltype(sizeof(U::type))); //sfinae happens here so no hard error here
void bar();
};
int main()
{
D<C> d;
}