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?
I was expecting this error to occur
[with T = MyClass3]
, rather thanMyClass1
. That is whenMyAlias
becomes an alias for typevoid
. Since you cannot have an object of typevoid
, the declaration of variableinputs
will trigger an error.You could add yet more SFINAE to disable the declaration of variable
inputs
whenMyAlias
is typevoid
. There is, however, a much simpler approach. Write a partial specialization for classInterface
that is used only when typeT
has a member typeMyStruct
.The implementation below uses a "roll-your-own" definition for
void_t
that I found on CppReference. This complies the with OP's specification thatstd::void_t
cannot be used.Enable individual members of the
Interface
In his comment below, the OP indicates that he has three different
MyStruct
types that are part of theInterface
:MyStructA
,MyStructB
, andMyStructC
.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 oftypename U = T
. This forces the compiler to defer, for instance, the evaluation oftypename 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
, andMyStructC
.CppReference supplied the code for a "roll-your-own" version of
enable_if_t
.