Reputation: 751
I'm creating a wrapper for a json decoder. Depending on the actual target different json parser 3rd parties are in use. All of them share a common interface.
class IJsonDecoder
{
public:
/**
* @brief Decodes the json object to a user speicifed type
* @tparam T Type to decode to
* @return decoded type
*/
template<typename T>
T decodeTo() const;
/**
* @brief Returns the value of a specfied key
* @tparam T Type of the value
* @param strKey Name of the key. nested keys are possible for ex key.subkey.subsubkey
* @return Value of the key
*/
template<typename T>
T getKeyValue(const std::string& strKey);
}
Now of course the usal way would be use virtual functions. However virtual functions have some unnecessary run time overhead (I know at compile time which 3rd Party to use).
I already use concepts to enforce some simple interfaces on classes. What I can't figure out is how to make concepts depend on a template parameter itself
///this is a simple interface
template <typename T>
concept ISomeSimpleIF= requires(T a)
{
{a.data() } -> std::convertible_to<std::string>; ///< Convertible to std::string
{a.empty() } -> std::same_as<bool>; ///< Check if data is empty
}
How can I make this depend on the return value T
///this defines the Interface for IJsonDecoder
template <typename JsonDecoderType_T, typename returnValue_T>
concept IJsonDecoder = requires(JsonDecoderType_T a, returnValue_T b)
{
{a.decodeTo() } -> std::same_as<returnValue_T >;
{a.getKeyValue() } -> std::same_as<returnValue_T >;
};
This throws a compiler error missing "<" when applying this concept
template <IJsonDecoder S>
void foo(S) {};
Is there a way to make concepts depend on other template parameters? thx for your help
Upvotes: 2
Views: 170
Reputation: 120059
Using CRTP:
template <class Impl>
class IJsonDecoder
{
public:
template<typename T> T decodeTo() const
{
return (static_cast<const Impl*>(this))->template decodeTo<T>();
}
template<typename T> T getKeyValue(const std::string& strKey)
{
return (static_cast<Impl*>(this))->template getKeyValue<T>(strKey);
}
};
// elsewhere
template <class JsonDecoderImpl>
void someFunction(IJsonDecoder<JsonDecoderImpl>& jsonDecoder)
{
std::string s = jsonDecoder.template decodeTo<std::string>(); // e.g.
}
Implementations need to declare themselces like that:
class MyJsonDecoder : public IJsonDecoder<MyJsonDecoder>
{
// ...
};
In your code that knows the implementation at compile time you can have:
MyJsonDecoder myDecoder;
someFunction(myDecoder);
and this will work, while
someFunction(notADecoder);
will induce a clear, short, user-friendly error message (as opposed to a hailstorm of template-induced error messages otherwise).
No concepts were harmed while producing this answer.
Upvotes: 1
Reputation: 63142
The concept IJsonDecoder
has two type parameters. When you use it as a template type constraint, the first is supplied for you, You still have to supply the second.
template <IJsonDecoder<SomeConcreteTypeThatFooExtracts> S>
void foo(S) {};
This does highlight that your concept IJsonDecoder
doesn't match your original class IJsonDecoder
. The class promises to be able to convert to every type, whereas the concept promises to convert to one type.
There is no concept that corresponds to your class, because that requires universal quantification.
Here is a concept that creates a family of constraints that your class satisfies.
template <typename JsonDecoderType_T, typename returnValue_T>
concept IJsonDecoder = requires(JsonDecoderType_T a, std::string b)
{
{ a.decodeTo<returnValue_T>() } -> std::same_as<returnValue_T >;
{ a.getKeyValue<returnValue_T>(b) } -> std::same_as<returnValue_T >;
};
Upvotes: 1