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?
With C++20, and specialization with concept, you might do
Demo