You may have a base class that implements only partial functionality and relies on the derived class to do the rest. How do you make sure that the derived class does the rest?
For concreteness, let’s say that we are implementing IValueConverter, which has two methods:
Convert()to convert from the source to the destination.ConvertBack()so that two-way conversions can convert from the destination to the source.
Suppose you want to write a base class called OneWayConverter. Its implementation fails the ConvertBack() call, and you want to force the derived class to implement the forward conversion.
// C++/WRL
struct WidgetColorConverter :
Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
ABI::Windows::UI::Xaml::Data::IValueConverter>,
OneWayConverter
{
// Require the derived class to implement Convert somehow
HRESULT STDMETHODCALLTYPE Convert(IInspectable* value,
ABI::Windows::UI::Xaml::Interop::TypeName targetType,
IInspectable* parameter, HSTRING language,
IInspectable** result)
{
⟦ ... ⟧
}
};
// C++/WinRT
struct WidgetColorConverter :
winrt::implements<WidgetColorConverter>, OneWayConverter
{
// Require the derived class to implement Convert somehow
winrt::Windows::Foundation::IInspectable
Convert(
winrt::Windows::Foundation::IInspectable const& value,
winrt::Windows::UI::Xaml::Interop::TypeName const& targetType,
winrt::Windows::Foundation::IInspectable const& parameter,
winrt::hstring const& language)
{
⟦ ... ⟧
}
}
// Plain C++ analogous scenario
struct WidgetColorConverter : OneWayConverter
{
// Require the derived class to implement Convert somehow
Color Convert(Widget const& value)
{
⟦ ... ⟧
}
};
During a code review, I saw that somebody tried to do this just by writing a comment.
// C++/WRL
struct OneWayConverter
{
// Derived classes must override this method.
HRESULT STDMETHODCALLTYPE Convert(IInspectable* /*value*/,
ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/,
IInspectable* /*parameter*/, HSTRING /*language*/,
IInspectable** result)
{
assert(false);
*result = nullptr;
return E_NOTIMPL;
}
// One-way converters cannot convert back
HRESULT STDMETHODCALLTYPE ConvertBack(IInspectable* /*value*/,
ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/,
IInspectable* /*parameter*/, HSTRING /*language*/,
IInspectable** result)
{
*result = nullptr;
return E_NOTIMPL;
}
};
// C++/WinRT
struct OneWayConverter
{
// Derived classes must override this method.
winrt::Windows::Foundation::IInspectable
Convert(
winrt::Windows::Foundation::IInspectable const& /*value*/,
winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/,
winrt::Windows::Foundation::IInspectable const& /*parameter*/,
winrt::hstring const& /*language*/)
{
assert(false);
throw winrt::hresult_not_implemented();
}
// One-way converters cannot convert back
winrt::Windows::Foundation::IInspectable
ConvertBack(
winrt::Windows::Foundation::IInspectable const& /*value*/,
winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/,
winrt::Windows::Foundation::IInspectable const& /*parameter*/,
winrt::hstring const& /*language*/)
{
throw winrt::hresult_not_implemented();
}
};
// Plain C++ analogous scenario
struct OneWayConverter
{
// Derived classes must override this method.
Color Convert(Widget const& /*value*/)
{
assert(false);
throw std::exception("not implemented");
}
// One-way converters cannot convert back
Widget ConvertBack(Color const& /*color*/)
{
throw std::exception("not implemented");
}
};
I pointed out that they were doing too much work.
The way to force somebody to implement a method in the derived class is simply not to implement the method in the base class in the first place.
// C++/WRL
struct OneWayConverter
{
// Derived classes must implement Convert()
// One-way converters cannot convert back
HRESULT STDMETHODCALLTYPE ConvertBack(IInspectable* /*value*/,
ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/,
IInspectable* /*parameter*/, HSTRING /*language*/,
IInspectable** result)
{
*result = nullptr;
return E_NOTIMPL;
}
};
// C++/WinRT
struct OneWayConverter
{
// Derived classes must implement Convert()
// One-way converters cannot convert back
winrt::Windows::Foundation::IInspectable
ConvertBack(
winrt::Windows::Foundation::IInspectable const& /*value*/,
winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/,
winrt::Windows::Foundation::IInspectable const& /*parameter*/,
winrt::hstring const& /*language*/)
{
throw winrt::hresult_not_implemented();
}
};
// Plain C++ analogous scenario
struct OneWayConverter
{
// Derived classes must implement Convert()
// One-way converters cannot convert back
Widget ConvertBack(Color const& /*color*/)
{
throw std::exception("not implemented");
}
};
The error message if they forget to implement it depends on the library.
// C++/WRL
struct WidgetColorConverter :
Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRt>,
OneWayConverter>
{
// Oops, forgot to implement Convert()
};
The error occurs when you call WRL:: to try to create the defective WidgetColorConverter.
wrl\implements.h(2512,32): error C2259: 'WidgetColorConverter': cannot instantiate abstract class
see declaration of 'WidgetColorConverter'
due to following members:
wrl\implements.h(2512,32):
'HRESULT ABI:: Windows:: UI:: Xaml:: Data:: IValueConverter:: Convert(IInspectable *,ABI:: Windows:: UI:: Xaml:: Interop:: TypeName, IInspectable *, HSTRING, IInspectable **)': is abstract
windows.ui.xaml.data.h(2778,59):
see declaration of 'ABI:: Windows:: UI:: Xaml:: Data:: IValueConverter:: Convert'
wrl\implements.h(2512,32):
the template instantiation context (the oldest one first) is
test(41,30):
see reference to function template instantiation 'Microsoft:: WRL:: ComPtr< WidgetColorConverter> Microsoft:: WRL:: Details:: Make<WidgetColorConverter,>(void)' being compiled
“Cannot instantiate abstract class due to the following members” is the standard error for failing to implement all the necessary pure virtual methods inherited from a base class, so one could expect that people who encounter this error will understand what it means.
// C++/WinRT
struct WidgetColorConverter :
winrt::implements<WidgetColorConverter, winrt::Windows::UI::Xaml::Data::IValueConverter>,
OneWayConverter
{
// Oops, forgot to implement Convert()
};
The error occurs when you call winrt:: to try to create the defective WidgetColorConverter.
windows.ui.xaml.data.h(1469,90): error C2039: 'Convert': is not a member of 'WidgetColorConverter'
test.cpp(66,8):
see declaration of 'WidgetColorConverter'
windows.ui.xaml.data.h(1469,90):
the template instantiation context (the oldest one first) is
test.cpp(66,31):
see reference to class template instantiation 'winrt:: implements<WidgetColorConverter, winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter>' being compiled
winrt\base.h(8088,31):
see reference to class template instantiation 'winrt:: impl:: producers_base<D, std::tuple< winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter>>' being compiled
with
[
D=WidgetColorConverter
]
winrt\base.h(6763,50):
see reference to class template instantiation 'winrt:: impl:: producer_convert<D, winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter, void>' being compiled
with
[
D=WidgetColorConverter
]
winrt\base.h(6734,31):
see reference to class template instantiation 'winrt:: impl:: producer<D, winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter, void>' being compiled
with
[
D=WidgetColorConverter
]
winrt\base.h(7137,23):
see reference to class template instantiation 'winrt:: impl:: produce<D,I>' being compiled
with
[
D=WidgetColorConverter,
I=winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter
]
winrt\windows.ui.xaml.data.h(1465,32):
while compiling class template member function 'int32_t winrt:: impl:: produce<D,I>:: Convert( void *, winrt:: impl:: struct_ Windows_ UI_ Xaml_ Interop_ TypeName, void *, void *, void **) noexcept'
with
[
D=WidgetColorConverter,
I=winrt:: Windows:: UI:: Xaml:: Data:: IValueConverter
]
“⟦Name⟧ is not a member of” is typical of a CRTP error, since the template is trying to call a method on the derived class, but it’s not there. Again, one could expect that people who encounter this error will understand what it means.
For the plain C++ case, you might have this:
// Plain C++ analogous scenario
struct WidgetColorConverter :
OneWayConverter
{
// Oops, forgot to implement Convert()
};
And everything works great until somebody tries to call the Convert method on a WidgetColorConverter and it’s not there.
test.cpp(79,20): error C2039: 'Convert': is not a member of 'WidgetColorConverter'
Again, this is a common error in C++ so you would hope that people understand what it means.
Great, so we were able to convert all of these authoring errors into compile-time errors, thereby avoiding the danger that somebody will use the base class and fail to implement all of the expected methods.
But wait, we can do better. We’ll look at this some more next time.
0 comments
Be the first to start the discussion.