简体   繁体   中英

explicit constructor still doing conversions

I have a (very) simple templated type that lets me return a "IsValid" flag when returning from functions. It goes as follows:

template <typename T>
struct Validated
{
private:
    T m_value;
    bool m_isValid;

public:
    Validated() : m_value(), m_isValid(false) {}
    explicit Validated(T const& value) : m_value(value), m_isValid(true) {}
    explicit Validated(bool isValid, T const& value) : m_value(value), m_isValid(isValid) {}
    explicit Validated(bool isValid, T&& value) : m_value(value), m_isValid(isValid) {}    

    bool IsValid() const { return m_isValid; }
    T const& Value() const { return m_value; }
};

Maybe there's something I don't understand with the explicit specifier, but I was wondering why the following works just fine, and how could I avoid the conversion from bool to double?

void someFunc()
    {
    Validated<double> foo(1.0); // this makes perfect sense
    Validated<double> bar(true); // works... (sets m_value to 1.0)
    }

Been looking at similar questions/answers but couldn't find any that is satisfactory. I am aware std::optional exists but we're not into c++17 yet. Tried this on VS2012/v110.

Update: as suggested, deleting the constructor for bool does the job (starting from c++14). It does not work for c++11 (VS2012/toolset v110).

You can simply delete the constructor taking a single bool :

Validated(bool value) = delete;

Note: You may need some extra precautions if you want Validated<bool> to be a valid type.


You can also prevent construction from any type other than T (stronger than the previous one):

template <class U>
Validated(U) = delete;

This will work even with Validated<bool> because construction from T will match your Validated(T const&) overload while construction from any type other than T will match the deleted template.

This method would prevent (even explicit) construction of Validated<double> from 1 , 1f , etc., so you may not want to use it.


explicit does not make your code ill-formed, it prevents implicit construction of a Validated<T> from a T , eg:

void f(Validated<double>);

f(1.0); // ill-formed because the conversion would be implicit

You could disable the Validated(bool) constructor in all cases apart from when T=bool Something like...

#include <iostream>
#include <type_traits>

using namespace std;
template <typename T>
struct Validated
{
private:
    T m_value;
    bool m_isValid;

public:
    Validated() : m_value(), m_isValid(false) {}


    explicit Validated(T const& value) : m_value(value), m_isValid(true) {}

    template <typename Y=T,typename std::enable_if<!std::is_same<Y,bool>::value,int>::type =0>
    explicit Validated(bool const& value) = delete;

    explicit Validated(bool isValid, T const& value) : m_value(value), m_isValid(isValid) {}
    explicit Validated(bool isValid, T&& value) : m_value(value), m_isValid(isValid) {}    

    bool IsValid() const { return m_isValid; }
    T const& Value() const { return m_value; }
};

int main() {
    Validated<bool> v(true);
    //Validated<int> v2(true); //fails
    Validated<int> v2(2);
    return 0;
}

Demo

The problem here is that argument passed to the constructor can be implicitly converted to constructor argument type. To prevent this you can convert constructor into a template and check that type of argument exactly matches template parameter:

template<typename TT> explicit
Validated(TT const& value) : m_value{value}, m_isValid{true}
{
   static_assert
   (
       ::std::is_same_v<TT, T>
   ,   "constructor argument type should match template parameter"
   );
}

online compiler

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM