I'm working on a template toolkit for declarative UI building. I have the templates compiling on Windows as expected but the GCC/clang compilers are taking issue with my SFINAE work.
I'm using Qt as the base, and I'm hoping to include QLayout and QWidget items in the wrapper structure while toggling on/off functions of the wrapper based on the object they represent.
They both derive from QObject which provides a ton of common ground, but I want to include their extended functionality.
On MSVC, the following works exceptionally well
// let cls be the input subclass of QWidget or
// QLayout which is passed to the owning wrapper template
template<typename = std::enable_if<std::is_base_of<QWidget, cls>::value == true>>
Wrapper &style() {
cast()->setStyleSheet(stylesheet);
return *this;
}
However GCC/clang don't agreed. I've tried using something akin to:
template<typename Empty = void,
typename = typename std::enable_if<
std::is_base_of<QWidget, cls>::value, Empty
>::type
>
Wrapper &style(const QString &stylesheet) {
cast()->setStyleSheet(stylesheet);
return *this;
}
But still get an error stating that QLayout subclasses don't have a member setStyleSheet
. And while it doesn't, I was hoping to have the compiler skip over the function entirely as it does in MSVC.
I'm assuming I have a misunderstanding of the SFINAE system and need to either adjust my template declaration, or just suck it up and split the two items and repeat the code.
EDIT:
Reproduction of Issue: https://gcc.godbolt.org/z/oGzsKhrn4
The top bits are just minimal reprs of Qt objects and it represents the same basic structure I'm after, you can mostly ignore them.
The macros are to ease the templating required for each class. If nothing else, I can always break it up and add additional macros to fill in QWidget vs. QLayout functionality, I just liked how clean it was for Windows.
CodePudding user response:
In your example, the behavior of style
function body is not dependent on its template parameters and a compiler can check the correctness. I guess this is allowed by the standard in [temp.res#general]:
The program is ill-formed, no diagnostic required, if:
...
— a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter,
...
However, you can make QVBoxLayout
a template default parameter as before (instead of Empty
), but make the body dependent on this parameter too:
template<typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}
But in this case we have an issue. Some user might accidentally pass this parameter and SFINAE will be broken, so I propose the following fix. Add a parameter pack as a first function template parameter. It will "eat" all accidentally passed parameters, so users won't be able to override the default parameters:
template<typename..., typename QVBoxLayout_ = QVBoxLayout...
And, of course, you can add an assertion that a user does not try to override these parameters unintentionally. In summary:
template<typename... ParameterGuard_,
typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
static_assert(sizeof...(ParameterGuard_) == 0,
"This function template is not intended to accept template parameters");
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}