enable_if cannot be used to disable this declaration
Asked Answered
I

5

12

I evidently have not enough experience with SFINAE to handle this problem. I actually have the impression that it worked until now, and this kind of problem started to appear like in the last half an hour, everywhere in my code.

#include <iostream>

using namespace std;

template <unsigned int N, typename = typename enable_if <N >= 100> :: type> 
struct more_than_99
{
};

int main()
{
    more_than_99 <0> c;
}

It says

No type named 'type' in 'std::__1::enable_if<false, void>'; 'enable_if' cannot be used to disable this declaration

on the line corresponding to the template declaration. What is going on? I have always used this kind of syntax to enable and disable my template classes and it has always thrown errors on the line of instantiation, rather than on the line of the declaration..

Could you please pedantically explain what am I doing wrong here?

Inappreciable answered 21/5, 2015 at 17:41 Comment(5)
enable_if isn't usually used to disable class types. What are you trying to accomplish here? more_than_99<500> seems to work fine.Girdler
I need an error to be thrown when trying to instantiate something like `more_than_99 <0> x;' on the line where I try to instantiate it. Something like "hey, this type doesn't exist".Inappreciable
@MatteoMonti That's what it's saying. Since bool is false, there's no T defined.Phytosociology
But... isn't there a way to say that on the line of the declaration?Inappreciable
It would make sense for the error message to point to both the declaration (for context) and the instantiation (not valid for this type, it might be valid for other types). But I'm pretty sure the error message is up to the compiler.Backgammon
G
4

The other answers are correct about why the error happens at the template definition rather than the instantiation.

I need an error to be thrown when trying to instantiate something like `more_than_99 <0> x;' on the line where I try to instantiate it. Something like "hey, this type doesn't exist".

How about something like this?

template <unsigned int N, bool B = (N>=100)>
struct more_than_99;

template <unsigned int N>
struct more_than_99<N,true>
{};

int main()
{
    more_than_99 <0> c; // error: implicit instantiation of undefined template 'more_than_99<0, false>'
}

To make it a bit more robust, and to attempt to prevent accidentally instantiating more_than_99<0,true>, this also works (C++11):

template <unsigned int N, bool B>
struct _impl_more_than_99;

template <unsigned int N>
struct _impl_more_than_99<N,true>
{};

template <unsigned int N>
using more_than_99 = _impl_more_than_99<N, (N>=100)>;

int main()
{
    more_than_99 <0> c; // error: implicit instantiation of undefined template '_impl_more_than_99<0, false>'
}

Although the error message references the _impl_ type.

You could hide the _impl_ in a detail namespace or something, and just document the more_than_99 alias as if it were the actual type.

However, you will not be able to prevent malicious instantiation of _impl_more_than_99<0,true>.

Girdler answered 21/5, 2015 at 17:58 Comment(2)
It won't for if you more_than_99<0, true>. Not very solid.Phytosociology
@black Even in the original example, more_than_99<0,void> would get around the enable_if. I'm not sure if it's possible to prevent malicious instantiation.Girdler
B
4

N is not a dependent non-type template parameter; [temp.dep.temp]/p2

A non-type template-argument is dependent if its type is dependent or the constant expression it specifies is value-dependent.

Therefore instead of a substitution failure occurring, the error is emitted directly from the ill-formed code.

Blodget answered 21/5, 2015 at 17:52 Comment(0)
G
4

The other answers are correct about why the error happens at the template definition rather than the instantiation.

I need an error to be thrown when trying to instantiate something like `more_than_99 <0> x;' on the line where I try to instantiate it. Something like "hey, this type doesn't exist".

How about something like this?

template <unsigned int N, bool B = (N>=100)>
struct more_than_99;

template <unsigned int N>
struct more_than_99<N,true>
{};

int main()
{
    more_than_99 <0> c; // error: implicit instantiation of undefined template 'more_than_99<0, false>'
}

To make it a bit more robust, and to attempt to prevent accidentally instantiating more_than_99<0,true>, this also works (C++11):

template <unsigned int N, bool B>
struct _impl_more_than_99;

template <unsigned int N>
struct _impl_more_than_99<N,true>
{};

template <unsigned int N>
using more_than_99 = _impl_more_than_99<N, (N>=100)>;

int main()
{
    more_than_99 <0> c; // error: implicit instantiation of undefined template '_impl_more_than_99<0, false>'
}

Although the error message references the _impl_ type.

You could hide the _impl_ in a detail namespace or something, and just document the more_than_99 alias as if it were the actual type.

However, you will not be able to prevent malicious instantiation of _impl_more_than_99<0,true>.

Girdler answered 21/5, 2015 at 17:58 Comment(2)
It won't for if you more_than_99<0, true>. Not very solid.Phytosociology
@black Even in the original example, more_than_99<0,void> would get around the enable_if. I'm not sure if it's possible to prevent malicious instantiation.Girdler
C
4

enable_if makes sense if you have a class specialization (or a function overload). It is used to choose between one implementation and another depending on a template parameter, not to trigger an error if the condition is not met.

The idea is "enable this specialization if the condition is met, otherwise fall back to the non-specialized version".

In your case, you probably want something like this:

#include <iostream>
#include <type_traits>

using namespace std;

template<unsigned int N, typename = void >
struct more_than_99
{
    // Implementation if N <= 99
    enum { value = false };
};

template <unsigned int N> 
struct more_than_99<N, typename enable_if <N >= 100> :: type>
{
    // Implementation if N >= 100
    enum { value = true };
};

int main()
{
    cout << more_than_99 <0>::value << endl; //false
    cout << more_than_99 <100>::value << endl; //true
}
Cadmus answered 21/5, 2015 at 20:15 Comment(1)
Thanks, that explains the concept behind std::enable_if (in term of SFINA).Bedlamite
F
1

From http://en.cppreference.com/w/cpp/types/enable_if: (emphasis mine)

This metafunction is a convenient way to leverage SFINAE to conditionally remove functions from overload resolution based on type traits and to provide separate function overloads and specializations for different type traits. std::enable_if can be used as an additional function argument (not applicable to operator overloads), as a return type (not applicable to constructors and destructors), or as a class template or function template parameter.

You cannot use it to enable or disable a class or a struct.

Perhaps you are looking for something like:

namespace detail
{
   struct more_than_99 {};

   template <bool> Helper;

   template <> Helper<true>
   {
      using type = more_than_99;
   };
}

template <unsigned int N> struct selector
{
   using type = typename detail::Helper<N >= 100>::type
};

using type = selector<10>::type; // Error.

using type = selector<100>::type; // OK.
                                  // type == detail::more_than_99
Flue answered 21/5, 2015 at 17:53 Comment(0)
M
1

Use static assert:

template<unsigned int N>
struct more_than_99
{
  static_assert(N >= 100, "N must be more than 99");
};

 more_than_99<1> m1;

Results in compilation error something along the lines of:

testM99.cpp:6:3: error: static_assert failed "N must be more than 99"
  static_assert(N >= 100, "N must be more than 99");
  ^             ~~~~~~~~
testM99.cpp:12:19: note: in instantiation of template class 'more_than_99<1>' 
requested here
  more_than_99<1> m1;
Mundford answered 6/10, 2020 at 22:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.