ruipacheco
ruipacheco

Reputation: 16422

How to prevent bool to int conversion in constructor?

I have the following class, as close as possible to my production code:

#include <iostream>

template <typename T>
struct M {

    M(std::string a, std::string b, T value = T(), const bool ready = false) : m_value{value}, m_ready{ ready } {}

    T m_value;
    bool m_ready;

};

auto main() -> int {
    {
        M<int> m{"a", "b"};
        std::cerr << m.m_value << std::endl;
    }
    {
        M<int> m{"a", "b", true};
        std::cerr << m.m_value << std::endl;
    }
}

In the first instance the value of m_value is 0 as expected. In the second it's 1 since it's taking the value of bool. Is there a way to avoid the conversion?

Upvotes: 8

Views: 699

Answers (3)

javidcf
javidcf

Reputation: 59701

I think this works

#include <iostream>
#include <type_traits>

template <typename T>
struct M {

    template <typename U=T, typename=std::enable_if_t<std::is_same_v<T, U>>>
    M(std::string a, std::string b, U value = U(), const bool ready = false) : m_value{value}, m_ready{ ready } {}

    T m_value;
    bool m_ready;

};

auto main() -> int {
    {
        M<int> m{"a", "b"};
        std::cout << m.m_value << std::endl;
    }
    {
        M<int> m{"a", "b", 1};
        std::cout << m.m_value << std::endl;
    }
    {
        // This does not compile
        // M<int> m{"a", "b", true};
        // std::cout << m.m_value << std::endl;
    }
    {
        // This compiles
        M<bool> m{"a", "b", true};
        std::cout << m.m_value << std::endl;
    }
    return 0;
}

Upvotes: 4

Barry
Barry

Reputation: 303087

You can add another constructor to reject anything that isn't exactly T:

template <typename T>
struct M {
    M(std::string a, std::string b, T value = T(), const bool ready = false);

    template <typename U>
    M(std::string, std::string, U, bool = false) = delete;
};

M<int>("hello", hello", true) will prefer the constructor template, which is deleted. But note that so will M<int>("hello", "hello", '4') as well as M<int>("hello", "hello", 4u). So it's a question of really working through which precise things you want to delete.

If you literally only want to reject exactly bool, you can do that by constraining the template:

template <typename U, std::enable_if_t<std::is_same_v<U, bool>, int> = 0>
M(std::string, std::string, U, bool = false) = delete;

Or, in C++20:

template <std::same_as<bool> U>
M(std::string, std::string, U, bool = false) = delete;

or:

M(std::string, std::string, std::same_as<bool> auto, bool = false) = delete;

Doing it this way would still allow M<bool> to be constructible from two strings and a bool, since the non-template constructor would still be a better match.

Upvotes: 5

Nicol Bolas
Nicol Bolas

Reputation: 473447

You can prevent the conversion by explicitly deleting a version that directly takes bool as the third parameter:

M(std::string, std::string, bool, bool = false) = delete;

However, if T is bool, that's going to cause problems. So you would need to use some SFINAE gymnastics to make this definition appear only when T is convertible to bool but isn't actually bool.

Upvotes: 7

Related Questions