Eagle
Eagle

Reputation: 3474

Validate types using templates at compilation time

I would like to get a compiler error in case I pass a float number to my class that expect an int in the code below. The code below runs with gcc 4.9.3

gcc -std=c++98 -O0 -g3 -Wall -Wextra -c -fmessage-length=0

How should I change the code that it will return compile error in case of different in types between the class instance type and the methods input argument type?

#include <iostream>
template <typename T>
class CheckValidity {
    public:
        CheckValidity(){};

        T isFoo(T variable)
        {
            T result = static_cast<T>(0);
            if ( variable > static_cast<T>(5) )
            {
                result = static_cast<T>(1);
            }
            return result;
        };
};

int main() {
    CheckValidity<int> checkValidityInt;
    std::cout << checkValidityInt.isFoo(6.0f) << std::endl;
    return 0;
}

Upvotes: 2

Views: 462

Answers (6)

Cody Gray
Cody Gray

Reputation: 244682

The reason you are not getting a warning now is because the float value 6.0f can be losslessly converted into an int. That is, you don't lose any precision during the conversion, because 6.0 can be exactly represented as 6. The compiler knows this because you are using compile-time constants.

If you changed it to 6.1f, for example, you should get a warning (assuming you have -Wconversion enabled):

int main() {
    CheckValidity<int> checkValidityInt;
    std::cout << checkValidityInt.isFoo(6.1f) << std::endl;
    return 0;
}
In function 'int main()':  
20 : warning: conversion to 'int' alters 'float' constant value [-Wfloat-conversion]  
std::cout << checkValidityInt.isFoo(6.1f) << std::endl;

You would also get a warning if you were passing a variable of type float, rather than a constant. This means the warning should catch any potential problems. A precise conversion of a constant is not a real problem.

To convert this warning into an error (making sure you do not miss it), also pass the -Werror flag.

Upvotes: 2

R Sahu
R Sahu

Reputation: 206557

Make the member function a template member function. It opens up a few possibilities for dealing with different types.


Use static_assert and std::is_floating_point to make sure that the function is not called using floating point types.

template <typename U>
T isFoo(U variable)
{
    static_assert(std::is_floating_point<U>::value == false,
                  "Can't handle floating point types");
    T result = static_cast<T>(0);
    if ( variable > static_cast<T>(5) )
    {
        result = static_cast<T>(1);
    }
    return result;
};

To make sure that U and T are the same type, you can use std::is_same:

template <typename U>
T isFoo(U variable)
{
    static_assert(std::is_same<T, U>::value,
                  "Can't handle different types");
    T result = static_cast<T>(0);
    if ( variable > static_cast<T>(5) )
    {
        result = static_cast<T>(1);
    }
    return result;
};

To be able to deal with U being a derived type of T, you can use:

template <typename U>
T isFoo(U variable)
{
    static_assert(std::is_base_of<T, U>::value,
                  "Can't handle incompatible types");
    T result = static_cast<T>(0);
    if ( variable > static_cast<T>(5) )
    {
        result = static_cast<T>(1);
    }
    return result;
};

Upvotes: 0

Jonathan Sharman
Jonathan Sharman

Reputation: 636

It sounds like you want to use std::is_integral in combination with static_assert.

In whatever function or class template you want to restrict to integers, you can call:

static_assert(std::is_integral<T>::value, "Integer required.");

You might also be interested in std::enable_if (or the convenience typedef enable_if_t):

template <typename Integer,
          typename = std::enable_if_t<std::is_integral<Integer>::value>>

You can use call static_assert with any integer constant expression to generate a compile-time error if the expression is false. You can use enable_if to restrict the types with which a template can be instantiated (also causing a compiler error if you use an invalid type).

Edit: I just noticed that you're using -std=c++98. If you need to use that standard, I can delete this answer. But if you can use C++11, this will be better than what you can do in earlier standards.

Upvotes: 0

Rakete1111
Rakete1111

Reputation: 48918

You can use template specialization:

//Returns false in the default case
template<typename V>
bool isFoo(V)
{
    return false;
};

//Returns true if the type passed is T
template<>
bool isFoo<T>(T)
{
    return true;
}

The variable is still passed as parameter without a name, because you don't need it internally. It is also still used because isFoo<decltype(var)>() is better than isFoo(var).


If you can use C++11, use std::is_same or similar constructs:

//You need a separate type, because if you use T, you will just cast the result to T
template<typename V>
bool isFoo(V)
{
    //Compare the types
    return std::is_same<V, T>::value;
};

Upvotes: 0

Zorgiev
Zorgiev

Reputation: 814

This effect is better achieved if you just leave common template class just declared, but not defined and specialize template class definition for float. I would try to play around something like type lists...

Upvotes: 0

user1937198
user1937198

Reputation: 5348

For c++98:

One way to achieve what you want of requiring an exact match is to have a template method which accepts any type but always fails due to SFINAE:

    template<typename U>
    U isFoo(U variable)
    {
      typename CheckValidity::InvalidType x;
    }

For c++11:

You can use a deleted method to achieve the same effect more cleanly:

    template<typename U>
    U isFoo(U variable) = delete;

Upvotes: 2

Related Questions