Home > database >  Is there a way to check if a class is convertible to some template instantiation?
Is there a way to check if a class is convertible to some template instantiation?

Time:02-11

The question

Here some code

struct Base
{
    int SomeMethod(const Base&);
    template <class T> int SomeMethod(const T&);
};

template <class Tag> struct Derived : Base
{
    using Base::SomeMethod;
    int SomeMethod(const Derived&);
    template <class OtherTag> std::enable_if_t<!std::is_same_v<Tag, OtherTag>> SomeMethod(Derived<OtherTag>) = delete;
};

struct tag {};
struct another_tag {};

struct ImplicitOpToAnotherTagInstantiation { operator Derived<another_tag>() const; };

Is there something (Derived::SomeMethod overload) that i can write in Derived without touching the Base, that will cause a compiler error in the next code?

Derived<tag>{}.SomeMethod(ImplicitOpToAnotherTagInstantiation {});

Next code should be correct

struct ImplicitOpToBase { operator Base() const; };

Derived<tag>{}.SomeMethod(Derived<tag>{});
Derived<tag>{}.SomeMethod(Base{});
Derived<tag>{}.SomeMethod(ImplicitOpToBase{});
// Derived<tag>{}.SomeMethod(Derived<another_tag>{}); compiler error!

What have I tried

  • First thought was to do something like this:
template <class Tag> struct Derived : Base
{
    using Base::SomeMethod;
    int SomeMethod(const Derived&);
    template <class OtherTag> std::enable_if_t<!std::is_same_v<Tag, OtherTag>> SomeMethod(Derived<OtherTag>) = delete;
    template <class T, class OtherTag> std::enable_if_t<!std::is_same<Tag, OtherTag> && std::is_convertible<Tag, Derived<OtherTag>>> SomeMethod(T) = delete;
};

But it doesn't work because there is no way for compiler to deduce OtherTag, without explicit instantiation like this

Derived<tag>{}.SomeMethod<ImplicitOpToAnotherTagInstantiation, another_tag>(); // error: deleted function!
  • Second thought:
template <class Tag> struct Derived : Base
{
    using Base::SomeMethod;
    int SomeMethod(const Derived&);
    template <class OtherTag> std::enable_if_t<!std::is_same_v<Tag, OtherTag>> SomeMethod(Derived<OtherTag>) = delete;
    template <class T> std::enable_if_t<!std::is_same<T, Derived> && std::is_convertible<T, Base>> SomeMethod(T) = delete;
};

But next code results in compiler error, when shouldn't:

Derived<tag>{}.SomeMethod(ImplicitOpToBase{});

Main usage

I am trying to create some kind of a strong typedef for std::string where Base is std::string, Derived is my TaggedString, SomeMethod is std::string::assign. I want to disable assign method with types of another tag and I have some difficulties explained in the question. For the sake of the reader i am omitting the fact, that TaggedString is implicitly convertible to std::string_view.

template <class Tag> struct TaggedString : std::string
{
    using std::string::assign;
    TaggedString& assign(const TaggedString& other) { std::string::assign(other); return *this; }
    template <class OtherTag> std::enable_if_t<!std::is_same_v<Tag, OtherTag>> assign(TaggedString<OtherTag>) = delete;
};

Here is something to play with.

CodePudding user response:

No. There's an infinite set of possible conversions, even an infinite set of conversions to Derived<SomeUnknownType>. Your compiler cannot test every possible type T to see if ImplicitOpToAnotherTagInstantiation is perhaps convertible to Derived<T>.

It could test a finite set, if you provide that. But you only have tag, a type to exclude. An infinite set of types minus one type is still an infinite set of types.

  • Related