65

I'm trying to specify a concept to constrain a higher kinded type that has a member function template using Concepts Lite. However I am not able to find inside the technical specification or the tutorial a clause dealing with templated statements inside a concept.

How is this done?

Example: suppose I have the higher kinded type HKT with a member function template F:

template<class T>
struct HKT {
  template<class U> // this looks like e.g. rebind in std::allocators
  auto F(U) -> HKT<U>;
};

and that now I want to specify a concept for constraining these higher kinded types:

template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) { // HKT<T> is a type, h is an object
    // HKT<T> needs to have a member function template that 
    // returns HTK<U> where the type U is to be deduced and
    // it can be any type (it is unconstrained)
    template<class U>  // is there a syntax for this?
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

Note that I could do something like:

template <template <class> class HKT, class T, class U>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
      h.F(std::declval<U>()) -> HKT<U>;
  }
}

but this means that I need to know U at constraint site.

I don't really care if substitution for a given U fails or not although I can see why this could be a problem: e.g. apply a constraint to be sure your function doesn't fail and then fails cause the constraint was satisfied but at instantiation time substitution failed in the member function template (would it help if the member function template was constrained?).

Rapptz
  • 20,807
  • 5
  • 72
  • 86
gnzlbg
  • 7,135
  • 5
  • 53
  • 106
  • `template auto F(U) -> HKT;` -- should this pass? How about `template::type>auto F(U)->HKT`? How about `templateauto F(U)->std::enable_if_t>`? How about `templateauto F(U)->std::enable_if_t>`? How about `templateauto F(U)->std::enable_if_t::value, HKT>`? – Yakk - Adam Nevraumont May 14 '14 at 19:26
  • @Yakk read the last paragraph please :) Would it help if instead of a member function template I would have a constrained member function template? – gnzlbg May 15 '14 at 17:59
  • I don't have a compiler handy that supports concepts lite, but afaiu, I can use using declarations, right? In that case, I could check for the existence of template F with `template using X = decltype(t.F(std::declval()));`. But I can't think of a way to make sure that X and HKT are equivalent. – Rumburak Aug 20 '14 at 13:54
  • Taking a step back from the syntax, I'll try to reformulate what you wan to do: `HKTWithTemplateMemberFunctionF` must test that `HKT` has a member function F that for each type `U` returns a value (convertible to) `HKT`. I can see how this can be tested for a specific `U` or a specific call in which U can be deduced, but in the context of the concept definition, I don't see how `U` can be specific and asking a compiler to test this for any type `U` requires a kind of higher order logic that I think is beyond the scope of concepts. – Maarten Hilferink Oct 11 '14 at 13:04
  • @MaartenHilferink Yes, after using a couple of concept checking libraries I don't think this is possible. The only alternative I can think of (for checking e.g. an allocator concept, is to have a refinement that includes rebind, and that you use at the moment in which you want to rebind the allocator. The error might then happen inside the instantiation of your container (e.g. in the instantiation of an internal implementation detail). But it would still tell you what went wrong. – gnzlbg Oct 18 '14 at 08:43

2 Answers2

9

Long story short, right now you (I?) have to provide a specific U:

template <template <class> class HKT, class T, class U = T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) { // HKT<T> is a type, h is an object
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

since the compiler cannot prove for all types U that might ever exist that the member-function template will work, that is, the following is hopeless:

template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
    template<class U>  // for all those Us that haven't been written yet...
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

In an hypothetical designed-in-5-min concepts-lite implementation where we are able to constrain U just a little-bit:

template <template <class> class HKT, class T, 
          InputIterator U = InputIterator()  /* imaginary syntax */ >
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
      h.F(std::declval<U>()) -> HKT<U>; // Is InputIterator enough to instantiate F?
  }
}

the compiler would only need to check if a model of InputIterator is enough to instantiate h.F, which is possible even if h.F is not constrained! Additionally providing U just checks that it models InputIterator, no need to even try to check h.F for U since InputIterator is enough. This could be used to optimize compile-time performance and...

...would probably interact in surprising ways with SFINAE, since AFAIK you can have a concept-overloaded function (e.g. for InputIterator) that accepts all input iterators except that one (SFINAE! why would anyone do that?!), and thus could pass concept-checking but blow at instantiation time... sad.

gnzlbg
  • 7,135
  • 5
  • 53
  • 106
4

Let's think about the requirements you want from your comment:

// HKT<T> needs to have a member function template that 
// returns HTK<U> where the type U is to be deduced and
// it can be any type (it is unconstrained)

While Concepts requires us to base our constraints around concrete types, we can be smart in our selection of which concrete types we use. What do you mean by U is any type. Really any type at all, whatsoever? Think about the smallest possible set of constraints you have on U and let's build a type that satisfies them. This is known as an archetype of U.

My goto first thought for "any type" would actually be a semiregular type. A type that is default constructible, copyable, and assignable. All the normal goodies:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };
}

archetypes::Semiregular is a concrete type, so we can use it to build a concept:

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF = 
  requires(HKT<T> h, archetypes::Semiregular r) {
    {h.F(r)} -> HKT<archetypes::Semiregular>
  };

archetypes::Semiregular is a private type. It should not be known to HKT, and so if h.F(r) is well-formed and returns a type convertible to HKT<archetypes::Semiregular>, it's almost certainly a member function template.

The question then is, is this a good archetype? Do we need U to be semiregular, or would irregular types work too? The fewer operations that you need, the fewer should be present in your archetype. Maybe all you need is that U is movable:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };

    struct Moveable {
        Moveable() = delete;
        Moveable(Moveable&& ) noexcept(false);
        Moveable(Moveable const& ) = delete;
        ~Moveable() = default;

        Moveable& operator=(Moveable const& ) = delete;
        Moveable& operator=(Moveable&& ) noexcept(false);
    };
}

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
  requires(HKT<T> h, archetypes::Moveable m) {
    { h.F(m) } -> HKT<archetypes::Moveable>
  };

We're testing the same idea - invoking F() with a type that isn't well-known and excepting the return type to reflect that, hence requiring it to be a function template. But now we're giving less functionality to the type. If F() works on any, it'll work on archetypes::Moveable.

Keep iterating on this idea until you've really pared down the required functionality to the bare minimum. Maybe you don't even need the archetype to be destructible? Writing archetypes is hard, but in cases like this, it's important to get right.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • A problem with this proposed solution is that even if you prove something for your archetype, there is nothing preventing the HKT from behaving in a completely different manner for other types due to template specialization. – Victor Savu Nov 01 '17 at 13:03
  • 1
    @VictorSavu Not specializing, just overloading. `h.F(0)` could just return an `int`, yes. But this approach tests what you actually want to test, and covers a lot of ground. There's only so much you can do. – Barry Nov 01 '17 at 13:19
  • Oh, you are totally right! There is no way specialization can interfere with the check, since F cannot be (fully) specialized so it will have to have the same kind of signature. – Victor Savu Nov 01 '17 at 15:05