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.