Reputation: 372
I'm working on a property system in C++ (C++11 is ok too) so that classes can offer data to a scripting system. I'd like to offer various "types" of properties, the simplest one holding its value internally, like this:
// Simple Property
template<typename Value>
class Property {
public:
Value get() const;
void set(Value value);
private:
Value m_value;
}
For most cases, that's ok, but sometimes, the value is calculated or has to be outside the property object for other reasons. For such cases, I'd like to offer a template like this:
// Complicated Property
template<
typename Value,
typename ValueOwner,
Value (ValueOwner::*Getter)(void) const,
void (ValueOwner::*Setter)(Value)
>
class Property {
// Stuff for calling the getter & setter
}
Is there any way I can make those two templates coexist without renaming one of them? I tried this to specialize the Getter / Setter template:
// Complicated Property
// ...
// Simple Property
template<typename Value>
class Property<Value, void, nullptr, nullptr>;
But the compiler doesn't like that and complains about creating pointer to member function non-class type "const void"
. Duh, of course void has no member functions.
Then I tried to declare the template with sufficient arguments and then specializing the above templates, like this:
template<typename A, typename B, typename C, typename D>
class Property;
// Simple Property
// ...
// Complicated Property
// ...
But that didn't work either, because the Getter / Setter template doesn't expect a typename as third and fourth arguments, but pointers to member functions.
Next try:
// Complicated Property
// ...
// Simple Property
template<typename Value>
class Property<
Value,
Property<Value>,
&Property<Value>::get,
&Property<Value>::set
>;
that failed to compile since the compiler doesn't now the Property<Value>
template inside the template arguments.
Alright, maybe this one:
// Complicated Property
// ...
template<typename Value>
struct PropertyDummy {
Value get() const;
void set(Value);
}
// Simple Property
template<typename Value>
class Property<
Value,
PropertyDummy<Value>,
&PropertyDummy<Value>::get,
&PropertyDummy<Value>::set
>;
Which yielded template argument involves template parameter(s)
for the getter and setter template arguments. Of course they do.
Anyway, renaming the templates to something like Property
and SimpleProperty
works for me and is good enough. But I'm curious if there's a solution so that I can write both
Property<int> simpleProperty;
Property<int, MyClass, &MyClass::get, &MyClass::set> complicatedProperty;
And have the compiler figure out which template I meant.
Upvotes: 2
Views: 1321
Reputation: 126562
But I'm curious if there's a solution so that [...]
Yes, there is.
You could use the following approach. First, create a dummy class template in some namespace that clients should not cope with:
namespace detail
{
template<typename T>
struct dummy
{
T get() const { return T(); }
void set(T) { }
};
}
Then, define the following primary template:
#include <type_traits>
// Simple Property
template<typename Value,
typename ValueOwner = detail::dummy<Value>,
Value (ValueOwner::*Getter)(void) const = &ValueOwner::get,
void (ValueOwner::*Setter)(Value) = &ValueOwner::set,
bool = std::is_same<ValueOwner, detail::dummy<Value>>::value
>
class Property {
public:
Value get() const;
void set(Value value);
private:
Value m_value;
};
The value of the last dummy template argument will depend on whether dummy<Value>
is passed as the second argument (clients should not use the dummy
class template, of course).
Then, you could specialize your template for the case when the last argument is false
(meaning that some argument was provided for the second template parameter):
// Complicated Property
template<
typename Value,
typename ValueOwner,
Value (ValueOwner::*Getter)(void) const,
void (ValueOwner::*Setter)(Value)
>
class Property<Value, ValueOwner, Getter, Setter, false>
{
// Stuff for calling the getter & setter
};
Finally, you could use have the two declarations you proposed:
struct MyClass
{
int get() const { return 42; }
void set(int) { }
};
int main()
{
Property<int> simpleProperty;
Property<int, MyClass, &MyClass::get, &MyClass::set> complicatedProperty;
}
Here is a live compiling example.
Upvotes: 4
Reputation: 55079
Do the getters and setters need to be specified at compile time? You can just get the type using the Value
and ValueOwner
template parameters:
template<
typename Value,
typename ValueOwner,
>
class Property {
public:
typedef Value (ValueOwner::*Getter)(void) const;
typedef void (ValueOwner::*Setter)(Value);
Property(Getter getter, Setter setter)
: getter(getter), setter(setter) {}
// ...
private:
Getter getter;
Setter setter;
};
Then pass the appropriate function pointers to the constructor:
Property<int, MyClass> complicatedProperty(&MyClass::get, &MyClass::set);
Upvotes: 3