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.
