gct
gct

Reputation: 14563

Enabling partial template specialization for callable with unknown return type?

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:

  1. build a parser from a string literal for convenience
  2. otherwise wrap a parsing function (which is a callable)

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

Answers (1)

Justin
Justin

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
    >
{};

Live on Godbolt


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

Related Questions