Reputation: 39
I am provided with a set of classes that I cannot alter. (For the curious: these are automatically generated from Simulink models) These classes unfortunately make too many things publicly available. So, I decided to write an interface class for all these classes that encapsulates them and makes using them a little safer. However, I am having trouble to write one interface class (template) that works for all possible classes that might occur.
Most of the classes define a structure called MyStruct
that I need to use within the interface class. I do not want to use that name, though, for various reasons and would like to use an alias within the interface class instead. So, I ended up with the following code:
// Classes to interface with
class MyClass1
{
public:
struct MyStruct
{
double f1;
double f2;
} _myVar {};
};
class MyClass2
{
public:
struct MyStruct
{
double g1;
double g2;
double g3;
} _myVar {};
};
class MyClass3
{};
// Interface class
template<typename T>
class Interface
{
public:
using MyAlias = typename T::MyStruct;
void setVar(const MyAlias& newVar) {_object._myVar = newVar;};
void doSomething()
{
MyAlias inputs {};
}
private:
T _object {};
};
// Main function
int main()
{
Interface<MyClass1> i1; // compiles fine
Interface<MyClass2> i2; // compiles fine
Interface<MyClass3> i3; // compile error: no type named ‘MyStruct’ in ‘class MyClass3’
return 0;
}
This fails to compile because MyClass3
does not define the type MyStruct
. Therefore, the alias cannot be defined.
I tried to look into defining the alias and method only if the type is defined and stumbled upon the SFINAE concept. Unfortunately, I cannot use anything from the standard library in my code. So, things like std::enable_if
or std::void_t
are off the table. Artificial intelligence came up with the following solutions for the alias and the method:
template<typename U, typename = void>
struct check_mystruct {using type = void; };
template<typename U>
struct check_mystruct<U, decltype((void)U::MyStruct, void())> { using type = typename U::MyStruct; };
using MyAlias = typename check_mystruct<T>::type;
template<typename U = MyAlias>
void setVar(const U& newVar) {_object._myVar = newVar;};
void setVar(...) { /* do nothing */ };
As far as I understand it, this should work. The partial specialization of the check_mystruct
struct and the templated version of the method should only be used if S::MyStruct
exists.
However, if I put it together into the initial program like this:
// Classes to interface with
class MyClass1
{
public:
struct MyStruct
{
double f1;
double f2;
} _myVar {};
};
class MyClass2
{
public:
struct MyStruct
{
double g1;
double g2;
double g3;
} _myVar {};
};
class MyClass3
{};
// Interface class
template<typename T>
class Interface
{
public:
template<typename U, typename = void>
struct check_mystruct {using type = void; };
template<typename U>
struct check_mystruct<U, decltype((void)U::MyStruct, void())> { using type = typename U::MyStruct; };
using MyAlias = typename check_mystruct<T>::type;
template<typename U = MyAlias>
void setVar(const U& newVar) {_object._myVar = newVar;};
void setVar(...) { /* do nothing */ };
void doSomething()
{
MyAlias inputs {};
}
private:
T _object {};
};
// Main function
int main()
{
Interface<MyClass1> i1; // compiles fine
Interface<MyClass2> i2; // compiles fine
Interface<MyClass3> i3; // compiles fine
i1.doSomething(); // compile error: In instantiation of ‘void Interface<T>::doSomething() [with T = MyClass1]’: variable or field ‘inputs’ declared void
i2.doSomething();
i3.doSomething();
return 0;
}
I get a compile error saying
main.cpp: In instantiation of ‘void Interface<T>::doSomething() [with T = MyClass1]’:
main.cpp:58:19: required from here
main.cpp:44:17: error: variable or field ‘inputs’ declared void
44 | MyAlias inputs {};
| ^~~~~~
So, the alias is set to void
even though MyClass
has a type MyStruct
. I do not understand this. Can someone explain what went wrong? Is there another way to achieve what I am trying to do?
Upvotes: 0
Views: 126
Reputation: 117972
If you can use boost, you could use Boost.PFR to create individual getters for all the fields in _myVar
and specify exactly for what fields there should be setters.
In case T
doesn't have a MyStruct
, MyAlias
will alias a struct
with no member variables instead. Of course, no getters and setters will then be useable, but the rest of the functions that doesn't depend on MyStruct
can still be used.
#include <boost/pfr.hpp>
template <class T, std::size_t... S>
class Interface {
template <class, class = void>
struct get_alias_def { struct type {}; }; // for T's without MyStruct
template <class A>
struct get_alias_def<A, std::void_t<typename A::MyStruct>> {
using type = A::MyStruct;
};
public:
using MyAlias = typename get_alias_def<T>::type;
static constexpr size_t field_count = boost::pfr::tuple_size_v<MyAlias>;
template <std::size_t F>
using field_type = boost::pfr::tuple_element_t<F, MyAlias>;
static_assert(((S < field_count) && ...), "Setter index out of bounds");
template <std::size_t F> // one getter per field
const auto& get() const {
static_assert(F < field_count, "F is out of bounds");
return boost::pfr::get<F>(_object._myVar);
}
// setters for the fields specified in S...
template <std::size_t F>
void set(const field_type<F>& value) {
static_assert(((F == S) || ...), "No setter for field");
boost::pfr::get<F>(_object._myVar) = value;
}
template <std::size_t F>
void set(field_type<F>&& value) {
static_assert(((F == S) || ...), "No setter for field");
boost::pfr::get<F>(_object._myVar) = std::move(value);
}
void doSomething() { /*...*/ }
private:
T _object{};
};
Usage example:
int main() {
Interface<MyClass3> i3;
// i3.get<0>(); // error: no fields available
Interface<MyClass2, 1, 2> i2; // only field 1 and 2 has setters
i2.set<1>(3.141); // ok, setter defined
std::cout << i2.get<1>() << '\n'; // 3.141
// i2.set<0>(3.141); // error, "No setter for field"
}
Upvotes: 0
Reputation: 2206
So, the alias is set to void even though MyClass has a type MyStruct. I do not understand this. Can someone explain what went wrong?
I was expecting this error to occur [with T = MyClass3]
, rather than MyClass1
. That is when MyAlias
becomes an alias for type void
. Since you cannot have an object of type void
, the declaration of variable inputs
will trigger an error.
Is there another way to achieve what I am trying to do?
You could add yet more SFINAE to disable the declaration of variable inputs
when MyAlias
is type void
. There is, however, a much simpler approach. Write a partial specialization for class Interface
that is used only when type T
has a member type MyStruct
.
The implementation below uses a "roll-your-own" definition for void_t
that I found on CppReference. This complies the with OP's specification that std::void_t
cannot be used.
// main.cpp
namespace tbx
{
// Definitions for C++11 from CppReference
// https://en.cppreference.com/w/cpp/types/void_t
template< typename... Ts>
struct make_void { typedef void type; };
template< typename... Ts>
using void_t = typename tbx::make_void<Ts...>::type;
}
// Classes to interface with
class MyClass1
{
public:
struct MyStruct
{
double f1;
double f2;
} _myVar{};
};
class MyClass2
{
public:
struct MyStruct
{
double g1;
double g2;
double g3;
} _myVar{};
};
class MyClass3
{};
// Interface class
// The base template for class `Interface` makes no mention
// of type `MyStruct`.
template<typename T, typename = void>
class Interface
{
public:
void doSomething()
{}
private:
T _object{};
};
// Interface class
// The partial specialization will be selected when type `T`
// defines member type `MyStruct`.
template<typename T>
class Interface<T, typename tbx::void_t<typename T::MyStruct>>
{
public:
using MyAlias = typename T::MyStruct;
void setVar(const MyAlias& newVar) { _object._myVar = newVar; };
void doSomething()
{
MyAlias inputs{};
}
private:
T _object{};
};
// Main function
int main()
{
Interface<MyClass1> i1; // compiles fine
Interface<MyClass2> i2; // compiles fine
Interface<MyClass3> i3; // compiles fine
return 0;
}
// end file: main.cpp
Interface
In his comment below, the OP indicates that he has three different MyStruct
types that are part of the Interface
: MyStructA
, MyStructB
, and MyStructC
.
The following program shows a variation of class template Interface
in which each member function can be enabled or disabled individually. Note the idiomatic use of typename U = T
. This forces the compiler to defer, for instance, the evaluation of typename U::MyStruct inputs{};
, until the template is actually instantiated.
The techniques used here serve as an example on how to "turn on and off" different members based on the presence or absence of MyStructA
, MyStructB
, and MyStructC
.
CppReference supplied the code for a "roll-your-own" version of enable_if_t
.
// main.cpp
namespace tbx
{
// Definitions for C++11 from CppReference
// https://en.cppreference.com/w/cpp/types/void_t
template< typename... Ts>
struct make_void { typedef void type; };
template< typename... Ts>
using void_t = typename tbx::make_void<Ts...>::type;
// Also from CppReference
// https://en.cppreference.com/w/cpp/types/enable_if
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
template< bool B, class T = void >
using enable_if_t = typename enable_if<B, T>::type;
}
// has_MyStruct_v
template< typename T, typename = void >
struct has_MyStruct {
static constexpr bool value = false;
};
template< typename T >
struct has_MyStruct<T, tbx::void_t<typename T::MyStruct>> {
static constexpr bool value = true;
};
template< typename T >
static constexpr bool has_MyStruct_v = has_MyStruct<T>::value;
// Classes to interface with
class MyClass1
{
public:
struct MyStruct
{
double f1;
double f2;
} _myVar{};
};
class MyClass2
{
public:
struct MyStruct
{
double g1;
double g2;
double g3;
} _myVar{};
};
class MyClass3
{};
// Interface class
template<typename T>
class Interface
{
public:
template< typename U = T, typename = tbx::enable_if_t<has_MyStruct_v<U>>>
using MyAlias = typename U::MyStruct;
template< typename U = T, typename = tbx::enable_if_t<has_MyStruct_v<U>>>
void setVar(const MyAlias<U>& newVar) { _object._myVar = newVar; };
template< typename U = T, typename = tbx::enable_if_t<has_MyStruct_v<U>>>
void setVar2(const typename U::MyStruct& newVar) { _object._myVar = newVar; };
template< typename U = T, typename = tbx::enable_if_t<has_MyStruct_v<U>>>
void doSomething()
{
MyAlias<U> inputs{}; // this works
typename U::MyStruct inputs2{}; // and this, too!
}
private:
T _object{};
};
// Main function
int main()
{
Interface<MyClass1> i1; // compiles fine
Interface<MyClass2> i2; // compiles fine
Interface<MyClass3> i3; // compiles fine
i1.doSomething();
i2.doSomething();
i1.setVar({});
i2.setVar({});
i1.setVar2({});
i2.setVar2({});
return 0;
}
// end file: main.cpp
Upvotes: 1
Reputation: 218268
With C++20, and specialization with concept, you might do
// Interface class
template <typename T>
class Interface
{
public:
void doSomething() {}
// ...
private:
T _object {};
};
template<typename T>
requires (requires {
typename T::MyStruct;
})
class Interface<T>
{
public:
using MyAlias = typename T::MyStruct;
void setVar(const MyAlias& newVar) {_object._myVar = newVar;};
void doSomething()
{
[[maybe_unused]]MyAlias inputs {};
}
private:
T _object {};
};
Upvotes: 3