Search code examples
c++templatesenable-if

std::is_base_of and error with if


I've got problem with templates:

I have got two constructors and method: .cpp:

Cell::Cell(sf::Vector2i& uPos, sf::Vector2f& cellDimensions, std::string& stateName)
    :unitPosition(uPos)
{
    setBasicParameters<std::string>(stateName,cellDimensions,uPos);
}

Cell::Cell(sf::Vector2i & uPos, sf::Vector2f & cellDimensions, int stateNumber)
    :unitPosition(uPos)
{
    setBasicParameters<int>(stateNumber,cellDimensions,uPos);
}

.hpp::

//Basic parameters which are being used by constructors
    template < typename T = typename std::enable_if< std::is_base_of<int, T>::value, T>::type || typename std::enable_if< std::is_base_of<std::string, T>::value, T>::type>
    void setBasicParameters(T& object, sf::Vector2f& cellDimensions, sf::Vector2i& uPos);

template<typename T>
inline void Cell::setBasicParameters(T& object, sf::Vector2f& cellDimensions, sf::Vector2i& uPos)
{
    shape.setSize(cellDimensions);
    shape.setOutlineThickness(cellDimensions.x / 10.0f); //10%

    shape.setOutlineColor(constants::cell::FILL_COLOR);

    shape.setPosition(uPos.x*cellDimensions.x, uPos.y*cellDimensions.y);

    if (!StateSystem::isStateExist(object))
    {
        Logger::Log(constants::error::stateSystem::STATE_DOES_NOT_EXIST, Logger::STREAM::BOTH, Logger::TYPE::ERROR);
        state = StateSystem::getNumberOfState(constants::defaults::EMPTY);
    }
    else
    {
        if (std::is_base_of<std::string, T>::value)
            state = StateSystem::getNumberOfState(object);  
        else state = object;

        setColor(StateSystem::getColorOfState(state));
    }
}

and problem is there:

if (std::is_base_of<std::string, T>::value)
            state = StateSystem::getNumberOfState(object);  
        else state = object;

In this if, I check a type of T, and if it is std::string, I use method from StateSystem which changes name to number. In the other way, if T is int, I don't need to change it so I am immediately assign T to state(state is int). But my compiler checks the dwo options and gives me errors:

Severity    Code    Description Project File    Line    Suppression State

Error C2440 '=': cannot convert from 'std::string' to 'uint8_t'

Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'int8_t mv::StateSystem::getNumberOfState(std::string)': cannot convert argument 1 from 'int' to 'std::string'  

Can I repair it without do two diffrent methods?


Solution

  • The problem is that the if statement here...

    if (std::is_base_of<std::string, T>::value)
    

    ...is a run-time branch. Even though is_base_of can be evaluated at compile-time, the compiler is forced to compile both branches of the if statement, even if their correctness relies on the is_base_of condition.


    Can I repair it without two different methods?

    C++17 introduces if constexpr (...), which does the branching at compile-time. This still requires both branches to be parseable, but only instantiates the one that matches the predicate. Therefore the non-taken branch can be "invalid" and your program will work as expected.

    if constexpr (std::is_base_of<std::string, T>::value)
        state = StateSystem::getNumberOfState(object);  
    else state = object;
    

    If you do not have access to C++14 and you really don't want to use two different functions, you can implement an equivalent construct to if constexpr(...). The implementation requires a significant amount of boilerplate. The final result will look like this:

    static_if(std::is_base_of<std::string, T>{})
        .then([&](auto){ state = StateSystem::getNumberOfState(object); })
        .else_([&](auto){ state = object; })(_);
    

    I gave a talk at CppCon 2016 and Meeting C++ 2016 called "Implementing static control flow in C++14" which explains how static_if works and how to implement it yourself.


    If you decide that using two different functions is acceptable, here's how you can solve the issue:

    if (!StateSystem::isStateExist(object))
    {
        // ...as before...
    }
    else
    {
        dispatch(state, std::is_base_of<std::string, T>{});
        // ...as before...
    }
    

    Where dispatch is defined as:

    void dispatch(State& state, Object& object, std::true_type)
    {
        state = StateSystem::getNumberOfState(object);
    }
    
    void dispatch(State& state, Object& object, std::false_type)
    {
        state = object;
    }