Reputation: 14563
I'm working on a parser combinator library, and I've got a class that defines what a parser is:
template <typename P, typename allow=void>
struct parser;
That I'm partially specializing to do two things:
If I can do that, then I can use is_convertible<T, parser<T>>
as a predicate for my combinators to disallow their use on anything that's not a "parser".
String is easy:
template <>
struct parser<string> {
...
};
But, in general a parser is just a callable that takes a parse_stream and returns an optional result (I'm in c++11 so I don't actually have std::optional, but we'll pretend). So, something like this:
template <typename Result>
using parser_callable = function<optional<Result>(parse_stream)>;
So how can I do a partial template specialization for a callable with an unknown return type? Note I don't want to convert to a std::function, just that it's a compatible callable "thing".
Edit, here's an example of my ideal capability if we had some sort of Scala-ish subtyping constraints and pattern matching...
template <P <: optional<R>(parse_stream)>
struct parser<P> {
parser(P p) : p_(p) {}
optional<R> operator(parse_stream stream) {
p_(stream);
}
private:
P p_;
};
Upvotes: 2
Views: 332
Reputation: 25277
Use SFINAE. Combined with a type trait is_optional
, you can enable the specialization for any callable type which returns an optional
when passed a parse_stream
:
template <typename T>
struct is_optional
: std::false_type
{};
template <typename T>
struct is_optional<optional<T>>
: std::true_type
{};
template <typename P, typename = void>
struct parser;
// This assumes that you want the parser to consume the `parse_stream`.
// It checks whether the parser can be called with the type `parse_stream&&`,
// which includes functions that take it by value, by `&&`, and by `const&`.
// If you had a specific one in mind, you can specify it. For example:
// std::declval<parse_stream&>() would pass a mutable reference instead
template <typename Callable>
struct parser<Callable,
typename std::enable_if<is_optional<
decltype(std::declval<Callable const&>()(std::declval<parse_stream>()))
>::value>::type
>
{};
std::declval<T>()
is a function which only works in unevaluated contexts, but it produces a value of type T&&
in such a context. Thus,
std::declval<Callable const&>()(std::declval<parse_stream>())
is an expression that's the same as having:
Callable const& callable = ...;
parse_stream some_parse_stream = ...;
// the following expression:
callable(std::move(some_parse_stream));
Since we use this in a context where SFINAE applies, if callable(std::move(some_parse_stream))
were an invalid expression, the specialization will be dropped from consideration.
Upvotes: 2