views:

479

answers:

4

Is there a way that I can create a function that takes an int template parameter, and have that function give a compile time error if the value passed to the function is less than 10?

The following code does not work, but it shows what I want to accomplish:

template <int number1>
void reportErrorIfLessThan10()
{
    #if(number1 < 10)
        #error the number is less than 10
    #endif
}


int maint(int argc, char**argv)
{
   reportErrorIfLessThan10<5>();//report an error!
   reportErrorIfLessThan10<12>();//ok
   return 0;
}
+2  A: 
template <int number1>
typename boost::enable_if_c< (number1 >= 10) >::type 
reportErrorIfLessThan10() {
    // ...
}

The above enable_if, without the _c because we have a plain bool, looks like this:

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

Boost's enable_if takes not a plain bool, so they have another version which has a _c appended, that takes plain bools. You won't be able to call it for number1 < 10. SFINAE will exclude that template as possible candidates, because enable_if will not expose a type ::type if the condition evaluates to false. If you want, for some reason, test it in the function, then if you have the C++1x feature available, you can use static_assert:

template <int number1>
void reportErrorIfLessThan10() {
    static_assert(number >= 10, "number must be >= 10");
}

If not, you can use BOOST_STATIC_ASSERT:

template <int number1>
void reportErrorIfLessThan10() {
    BOOST_STATIC_ASSERT(number >= 10);
}

The only way to display a descriptive message is using static_assert, though. You can more or less simulate that, using types having names that describe the error condition:

namespace detail {
    /* chooses type A if cond == true, chooses type B if cond == false */
    template <bool cond, typename A, typename B>
    struct Condition {
      typedef A type;
    };

    template <typename A, typename B>
    struct Condition<false, A, B> {
      typedef B type;
    };

    struct number1_greater_than_10;
}

template <int number1>
void reportErrorIfLessThan10() {
    // number1 must be greater than 10
    sizeof( typename detail::Condition< (number1 >= 10), 
             char, 
             detail::number1_greater_than_10 
            >::type ); 
}

It prints this here:

error: invalid application of 'sizeof' to incomplete type 'detail::number1_greater_than_10'

But I think the very first approach, using enable_if will do it. You will get an error message about an undeclared reportErrorIfLessThan10.

Johannes Schaub - litb
+5  A: 

If you don't want Boost C++ Libraries magic and want bare bones...

template<bool> class static_check
{
};

template<> class static_check<false>
{
private: static_check();
};

#define StaticAssert(test) static_check<(test) != 0>()

Then use StaticAssert. It's a #define for me because I have code that needs to run in a lot of environments where C++ doesn't work right for templates and I need to just back it off to a runtime assert. :(

Also, not the best error messages.

Joe
You could always use a typedef with a negative sub-script like I posted, if you can't use templates. Something like, #define StaticAssert(test) typedef char static_assert ## __LINE__[(test) ? -1 :1];
Jasper Bekkers
A: 

litb and Joe have already given the answers used in practice. Just to illustrate how this can be done manually by specializing for the number in question (rather than a general boolean condition):

template <int N>
struct helper : helper<N - 1> { };

template <>
struct helper<10> { typedef void type; };

template <>
struct helper<0> { }; // Notice: missing typedef.

template <int N>
typename helper<N>::type error_if_less_than_10() {
}

int main() {
    error_if_less_than_10<10>();
    error_if_less_than_10<9>();
}

Functions cannot be inherited but classes (and structs) can. Therefore, this code also uses a struct that automatically and dynamically generates cases for all N except 10 and 0, which are the hard-coded recursion begins.

By the way, the above code actually gives quite nice error messages:

x.cpp:16: error: no matching function for call to 'error_if_less_than_10()'
Konrad Rudolph
Just remember that most compiler have a limit on the depth of the recursion you do. Last time I checked, GCC defaulted to 50 or so. However, I don't know if the same holds true for this type of inheritance.
Jasper Bekkers
Well, that test is linear. That is, you will get instantiations for every N from your input down to 10 or 0. so if you set N=20000, you get 19990 base-classes. I crashed my operation system once, while trying to create too much instantiations of templates. it at least needs -ftemplate-depth-XXX :)
Johannes Schaub - litb
Yea, as I said, this code is more for illustration purposes than for anything else. There's no reason (technical or otherwise) to prefer this solution over the others.
Konrad Rudolph
+3  A: 

If for some reason you can't use Boost, this example is trivially written like this:

template <int number1>
void reportErrorIfLessThan10()
{
    typedef char number1_gt_10[number1 > 10 ? 1 : -1];
}


int maint(int argc, char**argv)
{
   reportErrorIfLessThan10<5>();//report an error!
   reportErrorIfLessThan10<12>();//ok
   return 0;
}

Or more generic

#define static_assert(test, message) typedef char static_assert_at_ ## __LINE__[(test) ? 1 : -1];

I'm not concatenating the error message itself, because I feel that static_assert(true, "some message"); is more readable than say static_assert(true, some_message);. However, this does limit the use case to only one assert per line.

Jasper Bekkers
The array bounds must be moved to the end of the line. Also, semicolon.
Konrad Rudolph
After all these years of C++, I still have trouble doing a normal typedef. Ugh.
Jasper Bekkers