Reputation: 16422
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
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
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 string
s and a bool
, since the non-template constructor would still be a better match.
Upvotes: 5
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