I have been trying to implement 'accessor' idiom and hit one of these
brick walls -- I need to prevent given operator from being generated
if underlying operation can't be compiled. Basically, this operator==:
template<class T> class accessor {
typedef accessor<T> Self;
public:
template<class C> friend inline bool operator==(Self l, C& r)
{ return l.m_ref == r; }
};
should not be instantiated if 'l.m_ref == r' fails to compile due to,
for example, absence of related operator==.
- I am familiar with SFINAE concept, but it seems I just can't get it
working.
- my compiler is MSVC 7.1
- I have tried boost::result_of without any success
I will be grateful for any solution, even those that works on GCC in
conjunction with 'typeof' operator.
Thanks
Bye.
Sincerely yours, Michael.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
> Basically, this operator==:
>
> template<class T> class accessor {
> typedef accessor<T> Self;
> public:
> template<class C> friend inline bool operator==(Self l, C& r)
> { return l.m_ref == r; }
> };
>
> should not be instantiated if 'l.m_ref == r' fails to compile due to,
> for example, absence of related operator==.
Unfortunately your snippet misses some clarifications,
the obvious one is that it is not clear, what type m_ref
belongs to, because its not defined above. In the following
I assume that m_ref has type T.
The code should solve your problem, I guess. It works for
Comeau 4.3.8 ALPHA, VS2003, VS2005-SP1, and mingw
with gcc 3.4.
template <bool Enable, typename T = void>
struct enable_if {
typedef T type;
};
template <typename T>
struct enable_if<false, T> {
};
namespace Details {
struct Shim
{
Shim(...); // Can be implicitely constructed by
// everything (Not defined).
};
struct NoMatchType
{
};
// Only declared, never defined:
NoMatchType operator==(const Shim&, const Shim&);
template <typename T, typename U>
struct IsEqualComparableImpl
{
private:
static const T& t();
static const U& u();
static char Check(bool); // Expected result
static char (&Check(const NoMatchType&))[2]; // Fallback
public:
static const bool value = sizeof(Check(t() == u())) == 1;
};
}
template <typename T, typename U>
struct IsEqualComparable {
static const bool value =
Details::IsEqualComparableImpl<T, U>::value;
};
template<class T> class accessor {
typedef accessor<T> Self;
public:
accessor() : m_ref() {}
template<class C> friend
inline
typename enable_if<IsEqualComparable<T, C>::value,
bool>::type
operator==(Self l, C& r) {
return l.m_ref == r;
}
private:
T m_ref;
};
class C{};
class D {};
bool operator==(D, D) { return true; }
bool test() {
accessor<D> a1;
D d;
bool b = a1 == d; // OK
accessor<int> a;
C c;
return a == c; // Error
}
int main() {
return test();
}
Greetings from Bremen,
Daniel Kr|gler
I don't think I understand the question completely; if the function
body won't compile, the template won't be generated; it will result in
a compile error. Isn't this what you want?
> template<class T> class accessor {
> typedef accessor<T> Self;
> public:
> template<class C> friend inline bool operator==(Self l, C& r)
> { return l.m_ref == r; }
>
> };
>
> should not be instantiated if 'l.m_ref == r' fails to compile due to,
> for example, absence of related operator==.
If you take this code example literally, it will never compile; m_ref
is not a member of accessor. I'll assume that there's a T & m_ref
member that you've omitted for brevity. Your operator== itself also
has a couple quirks. Why aren't you using const references to pass
arguments to the operator==? "C & r" won't match a literal constant,
which would limit its usefulness. Why are you passing the accessor by
value? Also, why not just use a member version of the operator?
It would probably help if you provided a more complete example of what
you're trying to accomplish.
> - I am familiar with SFINAE concept, but it seems I just can't get it
> working.
Get what working? Again, there's not a lot of info to go off of.
> - my compiler is MSVC 7.1
> - I have tried boost::result_of without any success
>
> I will be grateful for any solution, even those that works on GCC in
> conjunction with 'typeof' operator.
typeof? What is it that you're trying to do?
I haven't tried boost::result_of (though I don't know how that would
factor in anywhere), but I did try this example on MSVC 7.1:
template<class T> class accessor {
typedef accessor<T> Self;
T & m_ref;
public:
accessor(T & ref) : m_ref(ref) {}
template<class C> friend inline bool operator==(const Self & l,
const C & r)
{
return l.m_ref == r;
}
};
struct NullType {};
int main (int, char * []) {
NullType obj = {};
accessor<NullType> acc = obj;
if (acc == 1) {
return 1;
}
return 0;
}
I get this error:
e:\test\test\main.cpp(12) : error C3767: '==' matching function is not
accessible
could be the friend function at 'e:\test\test\main.cpp(10)' :
'==' [may be found via argument-dependent lookup]
e:\test\test\main.cpp(22) : see reference to function template
instantiation 'bool operator ==<int>(const accessor<T>::Self &,const C
&)' being compiled
with
[
T=NullType,
C=int
]
e:\test\test\main.cpp(12) : error C2676: binary '==' : 'NullType' does
not define this operator or a conversion to a type acceptable to the
predefined operator
This error prevents the operator== from compiling, and therefore being
instantiated. Task accomplished, as far as I can tell. Maybe there's
something else you're trying to do?
It errors, but why all the extra constructs? It will error inside the
accessor<int> operator== anyway, in addition to erroring when you try
to return a void in test().
I think that if you're going through the trouble of defining all the
extra stuff, it should be applied toward giving a descriptive error
inside the operator== :
...
// I'm just going to make this a member:
template<class C>
bool operator==(const C & r) {
BOOST_STATIC_ASSERT(IsEqualComparable<T, C>::value);
return m_ref == r;
}
...
Unless there's something that I'm missing, which is entirely possible;
I'm somewhat of an amateur at TMP.
Thanks! Exactly what I needed :-). There are couple of challenges
left, though:
- I'll need Is***<> template for every operator possible
- All of them should exclude implicit conversion (they'll be done by
corresponding conversion operator declared in accessor class) -- i.e.
if B and C can be converted to int IsEqualComparable(B,C) should be
"false", even if B() == C() will compile. This looks pretty
straightforward, actually... -- I guess I'll need to update your t() &
u() to return smth that could be converted (with user-defined
operator) to T & U respectively.
- and the latest and most scary -- I'll need to find result type of
every operator. So, at the end every operator should look like:
template<class U, class restriction = SFINAE<is_XXX_defined<T, U> > >
typeof(t XXX u) operator XXX(Self l, U& r) { return l.m_ref XXX r; }
(not mentioning const versions & version where both arguments have
type 'Self'). I guess this is possible only under GCC, since MSVC does
not have 'reliable' typeof -- I am not sure if existing hacks will
help me here.
Bye.
Sincerely yours, Michael.
> It errors, but why all the extra constructs? It will error inside the
> accessor<int> operator== anyway, in addition to erroring when you try
> to return a void in test().
1) I don't know what you mean with "in addition to erroring when
you try to return a void in test()" - can you explain?
2) These extra constructs exists basically for the same reasons,
why concepts - see e.g.
http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2036.pdf
http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2042.pdf
- are so useful: Instead of getting errors possibly deep inside
some template code you provoke a much clearer error at the
immediate surface.
> I think that if you're going through the trouble of defining all the
> extra stuff, it should be applied toward giving a descriptive error
> inside the operator== :
>
> ...
> // I'm just going to make this a member:
> template<class C>
> bool operator==(const C & r) {
> BOOST_STATIC_ASSERT(IsEqualComparable<T, C>::value);
> return m_ref == r;
> }
> ...
Yes of course, but this is the solution to just another problem.
Note that the OP did not want an error inside the actual function,
the intend was to remove this overload from a set of possible
multiple
canditates, *if* the requirements are not fulfilled. While this can be
very useful for different other functions (e.g. std::advance/
std::distance)
I also have no convincing example at my hands for this special
scenario.
Greetings from Bremen,
Daniel Krügler
I think that I'm beginning to understand what you're going for.
> - I'll need Is***<> template for every operator possible
> - All of them should exclude implicit conversion (they'll be done by
> corresponding conversion operator declared in accessor class) -- i.e.
> if B and C can be converted to int IsEqualComparable(B,C) should be
> "false", even if B() == C() will compile. This looks pretty
> straightforward, actually... -- I guess I'll need to update your t() &
> u() to return smth that could be converted (with user-defined
> operator) to T & U respectively.
Ah. I think what you want is the double-conversion trick. No more
than one implicit conversion can be used in any one conversion, so I
believe that what you want is to replace t() == u() with a function
that takes arguments that are implicitly convertible from T and U, and
has the equivalent of t() == u() in the body. Something like:
// This class has only one implicit conversion; that is, to a W &
template <class W> class ImplicitConvert {
W & ref_;
public:
// Make an explicit constructor
explicit ImplicitConvert(W & ref) : ref_(ref) {}
// Define an implicit conversion to W &
operator W & () { return ref_; }
// Don't define an operator==()!
}
You would replace "t() == u()" with "ImplicitConvert<T>(t()) ==
ImplicitConvert<U>(u())". This would ensure that the implicit
conversion is done by a step that you control (ImplicitConvert<T>(t())
to T &, for example), instead of the match for the operator==().
> - and the latest and most scary -- I'll need to find result type of
> every operator. So, at the end every operator should look like:
You'll probably need to define this explicitly for many operators.
What if A() + B() results in an instance of C?
> template<class U, class restriction = SFINAE<is_XXX_defined<T, U> > >
> typeof(t XXX u) operator XXX(Self l, U& r) { return l.m_ref XXX r; }
>
> (not mentioning const versions & version where both arguments have
> type 'Self'). I guess this is possible only under GCC, since MSVC does
> not have 'reliable' typeof -- I am not sure if existing hacks will
> help me here.
Since you're going through the effort of finding the return types of
all these operators, you could just specify it, instead of relying on
typeof, or maybe define a traits class for IsEqualComparable, or some
such. Additionally, you can usually count on (for example) operator==
taking const reference arguments, which will limit the specific cases
that you have to cover. It doesn't make sense for operator== to take
non-const arguments. The same kind of reasoning can be applied to
most operators. The case of T and U being the same class shouldn't
offer too much difficulty, but you could always define a
specialization that handled it if you had to.
This was my oversight; I assumed that the body of the operator== would
error, but that wouldn't be the case.
> 2) These extra constructs exists basically for the same reasons,
> why concepts - see e.g.
>
>
> - are so useful: Instead of getting errors possibly deep inside
> some template code you provoke a much clearer error at the
> immediate surface.
Oh, I would love to have concepts right now in the code that I write;
I've been waiting for that part of the standard to come through for
some time. :) Programming by contract is one of the most useful
designs there is, IMO.
> > I think that if you're going through the trouble of defining all the
> > extra stuff, it should be applied toward giving a descriptive error
> > inside the operator== :
>
> > ...
> > // I'm just going to make this a member:
> > template<class C>
> > bool operator==(const C & r) {
> > BOOST_STATIC_ASSERT(IsEqualComparable<T, C>::value);
> > return m_ref == r;
> > }
> > ...
>
> Yes of course, but this is the solution to just another problem.
>
> Note that the OP did not want an error inside the actual function,
> the intend was to remove this overload from a set of possible
> multiple
> canditates, *if* the requirements are not fulfilled. While this can be
> very useful for different other functions (e.g. std::advance/
> std::distance)
> I also have no convincing example at my hands for this special
> scenario.
I understand the question quite a bit better now; however, it was not
at all clear to me that the OP wanted an error at the point of
operator usage. He said, "Basically, this operator==: ... should not
be instantiated if 'l.m_ref == r' fails to compile due to, for
example, absence of related operator==." If the statement fails to
compile, the operator simply won't get instantiated due to a compiler
error. I didn't know that he wanted control over the location of the
error as well.
In a later post, the OP also explained the additional constraints that
he wanted on the operator. But these explanations didn't come until
then. It helps to have more of an explanation up front. Then I'd be
less aggressively inquisitive. :)
No more user-defined conversion... imho compiler could use any number
built-in conversions, and the are all implicit -- explicit conversions
are used only when you specifically ask for it... The rest is correct.
> You would replace "t() == u()" with "ImplicitConvert<T>(t()) ==
> ImplicitConvert<U>(u())". This would ensure that the implicit
> conversion is done by a step that you control (ImplicitConvert<T>(t())
> to T &, for example), instead of the match for the operator==().
yes, I need to exclude possible implicit conversions that could make
t() and u() comparable, while they could not be compared 'directly'.
> > - and the latest and most scary -- I'll need to find result type of
> > every operator. So, at the end every operator should look like:
>
> You'll probably need to define this explicitly for many operators.
> What if A() + B() results in an instance of C?
> ... [skip]
> Since you're going through the effort of finding the return types of
> all these operators, you could just specify it, instead of relying on
> typeof, or maybe define a traits class for IsEqualComparable, or some
> such. Additionally, you can usually count on (for example) operator==
> taking const reference arguments, which will limit the specific cases
> that you have to cover. It doesn't make sense for operator== to take
> non-const arguments. The same kind of reasoning can be applied to
> most operators. The case of T and U being the same class shouldn't
> offer too much difficulty, but you could always define a
> specialization that handled it if you had to.
My original idea was to implement accessor<T> such that any:
class A
{
T m_data;
public:
T const& data() const { return m_data; }
T& data() { return m_data; }
};
could be seamlessly replaced with
class A
{
T m_data;
public:
accessor<T const> data() const { return make_accessor(m_data); }
accessor<T> data() { return make_accessor(m_data); }
};
and this won't break any code regardless of type T. It was looking
simple first, then I ran into bewildering number of difficulties,
until I realised that:
- it could not be done without typeof (to detect return values for
oveloaded operators)
- it could be done only by overloading:
a) operators defined on T and only them (i.e. if T does not have
op==, accessor<T> should not have it too)
b) accessor<T> should be implicitly convertible to any type T is
convertible, but no more
Unfortuanately C++ does not have typeof, also it is very hostile to my
attempts to implement a) and b).
The next logical step was to augment accessor<> so that it could be
based not only on reference to T, but on some arbitrary functions that
return (get/set).
Bye.
Sincerely yours, Michael.