1

I am currently trying to get my feet wet with concepts. Let us assume that I have a concept:

template <class T> concept Initable = requires (T&t) { { init(t) }; };
  // just for demonstration purposes, real concept makes more sense ...

and I want to provide an adoption layer for a third-party class like std::optional to implement this concept, what would be the most seemless way for me to do so?

Obviously, the following piece of code fails:

template <std::semiregular T>
T& init(std::optional<T> &v) { /* contents not that important */ v = T{}; return *v; }

static_assert(Initable<std::optional<int>>, "Oh no!"); // Fails!

The reason is two-phase lookup.

When trying to resolve init in the Initable concept during phase 1, my definition of init is not available, because it is provided below the Initable concept.

When trying to resolve it during phase 2, my definition is not found via argument-dependent lookup, because it is not provided in the std namespace.

Two obvious solutions, thus, would be to either provide the definition of init before defining the Initable concept or move init to the std namespace.

But I want to implement that concept for std::optional without

  • relying on a particular definition/include order,
  • populating the std namespace and
  • using too much boiler-plate code at the caller site.

What would be the best way to do so? Could I make it somewhat easier to accomplish this when defining the Initable concept?

Basically I am asking, is this possible?

#include <concepts>
#include <optional>

template <class T>
concept Initable =
    requires (T& t) { init(t); } // May be changed to make the task easier.
;

// Insert code *here* that magically makes std::optional implement Initable.

static_assert(Initable<std::optional<int>>, "std::optional is not initable!");

And, if not, what would be the next best thing?

Markus Mayr
  • 4,038
  • 1
  • 20
  • 42
  • I do not understand what you are trying to do. It seems that there are too many components to your architecture that you are not clear on: there is what you implement (a library maybe?), the user (consumer of your code) and 3'rd party libraries. You define a concept, but what does it mean that concept "could be implemented by users"? Either you implement the concept or your users implement the concepts. It doesn't make sense for me personally. "for using them with my hypothetical library." You cannot add concepts to an existing library unless you own and rewrite the library. – bolov Jan 19 '21 at 10:19
  • @bolov: My library: defines concept, Third party library: provides class that does not implement the concept, User code: implements concept on top of third-party class, then wants to use that class as if it implemented the concept. When stating it that way, it boils down to extending the public interface of a third-party class. Now I realize that I might be asking for too much. – Markus Mayr Jan 19 '21 at 10:36
  • either I misunderstand you or you misunderstand concepts. A class doesn't "declare" that implements a concept. A class either satisfy a concept or not. If you define a concepts (e.g. default initializable) a class will satisfy this concept if it is default initializable. – bolov Jan 19 '21 at 10:49
  • you snippets of code are insufficient to understand your problem. Can you please create a small [MRE]? – bolov Jan 19 '21 at 10:50
  • @bolov: I tried to rephrase the question and hope that it is clearer now? – Markus Mayr Jan 19 '21 at 11:27
  • yes, it is clear to me now. Thank you. Note: (with few exceptions) opening namespace std is illegal so that is not an option for you. – bolov Jan 19 '21 at 11:36
  • look into [CPOs](https://stackoverflow.com/questions/53495848/what-are-customization-point-objects-and-how-to-use-them) they might be what you need, but they required ADL to work. – bolov Jan 19 '21 at 12:16
  • @bolov: Tried this, works fairly well. I'm using a CPO to dispatch via ADL to either a separate customization namespace or the namespace of the class. For 3rd-party libraries, the customization namespace can be used to define the required interface. Might turn this into an answer later on. – Markus Mayr Jan 19 '21 at 14:29
  • Please do write an answer! – bolov Jan 19 '21 at 15:03

1 Answers1

0

Resolving to any function that is visible during template instantiation is clearly against the principles of two-phase lookup. I decided to take a different approach instead and use a dummy parameter that is defined in a separate customization namespace:

namespace CP { // CP = CustomizationPoint
    struct Token {};
}

It is now possible, to define the init function in either the CP namespace or the namespace, the object is defined in:

namespace std {
    template <class T> void init(std::optional<T> &v, CP::Token) { ... }
    // illegal, but works -- do not declare stuff in namespace std.
}

namespace CP {
    template <class T> void init(std::optional<T> &v, CP::Token) { ... }
}

This is already pretty nice. It would be even nicer, if the definition in the std namespace would not require the CP::Token parameter. This can be done by providing a function object that will resolve to the correct function (similar to customization point objects that got introduced into the standard library):

constexpr inline struct {
    template <class T>
    auto operator()(std::optional<T>& v, CP::Token t) const
        -> decltype(init(v, t), []{}())
    {
        init(v, t);
    }

    template <class T>
    auto operator()(std::optional<T>& v, ...) const
        -> decltype(init(v), []{}())
    {
        init(v)
    }
} init;

This function object is a bit bulky, but it will resolve to the variant with the CP::Token parameter, if it is available and otherwise fall back to the variant without the token parameter, if that one is available.

To me, this seems to be a pretty sane approach that is perfectly extendable and even allows us to override an implementation that already has the right name, but the wrong semantics.

The concept must be modified slightly for this to work:

template <class T> concept Initable = requires (T&t) { init(t, CP::Token{}); };
Markus Mayr
  • 4,038
  • 1
  • 20
  • 42