Best extension mechanism to be able to adopt third-party dependencies? - c++

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?

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

Related

How to use ADL in Constraints?

As an example, I want to use a constraint to ensure that the function isinf is implemented for a template parameter T. If T is one of float, double, long double or an integral type, this can be done in the following way:
#include <cmath>
template <typename T>
concept Test = requires (T a) {
std::isinf(a);
};
However, I also want to use this constraint for custom, non-standard data types for which I implement my own isinf function. This function is not contained in the std namespace, so I tried the following instead:
#include <cmath>
template <typename T>
concept Test = requires (T a) {
using std::isinf;
isinf(a);
};
This does not work since every statement in the requires clause should be a valid requirement and using std::isinf is not a "requirement" at all.
I see two workarounds to this problem:
Move the using std::isinf; clause to the global namespace. But this introduces isinf to the global namespace, which I'd like to avoid.
Encapsulate the using std::isinf; clause with the concept definition in a namespace named ABC, then add using ABC::Test; directly after the namespace. This seems a little bit weird.
Is there a better solution?
The way this sort of thing works in Ranges is by creating a Customization Point Object. This closely mirrors your second option (we stick a using-declaration in a custom namespace) except we also provide an mechanism for users to call the correct isinf without having to write a bunch of the same kind of boilerplate themselves.
A customization point object for isinf would look something like this:
namespace N {
// make our own namespace
namespace impl {
// ... where we can bring in std::isinf
using std::isinf;
struct isinf_t {
// our type is constrained on unqualified isinf working
// in a context where std::isinf can be found
template <typename T>
requires requires (T t) {
{ isinf(t) } -> std::same_as<bool>;
}
constexpr bool operator()(T t) const {
// ... and just invokes that (we know it's valid and bool at this point)
return isinf(t);
}
};
}
// we provide an object such that `isinf(x)` incorporates ADL itself
inline constexpr auto isinf = impl::isinf_t{};
}
And now that we have an object, a concept follows directly:
template <typename T>
concept Test = requires (T t) {
N::isinf(t);
}
This is precisely how the range concept is specified.
This should work. I'm not that comfortable with c++20 concepts so I cannot guarantee that the syntax is correct.
namespace impl {
using std::isinf;
template<class T>
auto adl_isinf(T t) -> decltype(isinf(t));
// { return isinf(t); } // implementation not necessary depending on whether
// requires is an unevaluated context
}
template <typename T>
concept Test = requires (T a) {
impl::adl_isinf(a);
};

How to request vector<T> as a template type argument

In general, how would you request vector<T> as a type in a template?
template<?vector<T>?>
void allocate() {
vector<T> vector;
}
To be more specific (the example above is a bit dumb), I'm using a service_locator<T> from this library and all of its members are static. The constructor and the destructor of that struct are deleted, therefore there is no such thing as an instance of a service_locator<T>. I'd like to write a function that does something, given some service_locator<T>. Something like the following.
template<?service_locator<T>?> // What do I write here?
T& assertedRef() {
assert(!service_locator<T>::empty(), "Value not initialized for that type!");
return service_locator<T>::ref();
}
My current workaround is to ask for T instead of service_locator<T>. This is a hassle for the callers because it's on them to figure out the underlying type the service_locator is operating on (most of them are aliased to something more readable).
You can use partial specialization for this. Since that's not supported for functions, you have to forward the call to a partially specialized class instance.
template <typename T>
struct AssertedRefFunctor {
static_assert(sizeof(T) == -1, "Not a service locator");
};
template <typename T>
struct AssertedRefFunctor<entt::service_locator<T>> {
T& operator()() {
if (entt::service_locator<T>::empty()) {
throw std::runtime_error("Value not initialized for that type!");
}
return entt::service_locator<T>::ref();
}
};
template <typename T>
auto assertedRef() -> decltype(std::declval<AssertedRefFunctor<T>>()()) {
return AssertedRefFunctor<T>{}();
}
Usage:
assertedRef<entt::service_locator<int>>();
Demo: https://godbolt.org/z/f-oUpY
C++ (at least C++17) does not support template parameter constraints like C# does (they were proposed for C++0x as "concepts" but withdrawn). But you can have a static assert using std::is_base_of.
And as a hint to whoever consumes your library, I'd name the template parameter as TVector and TVectorItem to be clear that it expects a vector<TVectorItem> derivative.
(And I agree with the (now deleted) comment that the Service Locator pattern is an anti-pattern. There are almost always better alternatives to using the Service Locator pattern - but we'd need to know more about your application before suggesting anything).
(And if you're using Visual Studio or MSBuild and want to build a statically-evaluated or constexpr DI container builder, consider using T4 for codegen instead of C++ templates).
#include <type_traits>
template<typename TVector, typename TVectorItem>
void allocate() {
static_assert( std::is_base_of<vector<TVectorItem>, TVector>::value, "TVector must derive from vector<TVectorItem>." );
TVector vector;
}
There are no direct way to do that.
In your case, you might do:
template <typename ServiceLocatorT>
auto assertedRef() -> decltype(ServiceLocatorT::ref())
{
assert(!ServiceLocatorT::empty(), "Value not initialized for that type!");
return ServiceLocatorT::ref();
}
In generic case, template classes might have aliases of there template (as done with STL std::vector<T>::value_type) that you can use.
At worst, you might create type_traits:
template <typename T> struct value_type;
template <typename T> struct value_type<std::vector<T>>
{
using type = T;
};
// ...

What is the rationale behind ADL for arguments whose type is a class template specialization

I've spent some time trying to realize why my code doesn't compile and I've realized that in C++ Argument Dependent Lookup uses template typename arguments to determine name lookup scope.
#include <string>
#include <functional>
namespace myns {
template<typename T>
struct X
{};
template<typename T>
auto ref(T) -> void
{}
} // namespace myns
auto main() -> int
{
ref(myns::X<int>{});
ref(myns::X<std::string>{}); // error: call to 'ref' is ambiguous
}
So the former ref call compiles, because for myns::X<int> only myns::ref is considered, while the latter doesn't compile because it finds myns::ref() as well as std::ref
My question is how this can be useful? Why would I need this? Do you have any ideas, examples? For now I can only see drawbacks like in the example above, where it introduces unneeded ambiguity.
Suppose you put all the things into your own namespace, including a user-defined class, and a function which takes std::vector as the parameter. i.e.
namespace myns {
struct X {};
template<typename T>
auto my_func(const std::vector<T>&) -> void
{}
} // namespace myns
then you can take advantage of the fact that ADL also considers the types provided as template arguments and just write:
my_func(std::vector<myns::X>{});
on the other hand:
my_func(std::vector<int>{}); // error, can't find my_func
myns::my_func(std::vector<int>{}); // fine
Get back to your original question, the lesson here is don't use names from standard libraries, it just makes codes confusing.
In one word: reuse. It allows you to use useful components from other libraries, and still have ADL applied.
For instance:
namespace my_stuff {
class my_class {
// Something useful here
};
void process(std::unique_ptr<my_class> item);
}
Now you can write code naturally, as you would when working with the class directly:
process(std::make_unique<my_class>());
If it wasn't the case, you'd need to roll out your own smart pointer, in your own namespace, just to facilitate good coding idioms and ADL.

Customization points and ADL

I am writing a library and there is a function that performs an (unqualified) call to free function foo using an arbitrary type as argument:
namespace lib {
template <typename T>
auto libfunc(T && t)
{
return foo(std::forward<T>(t));
}
} // namespace lib
A user of the library can write an overload of foo for his own types:
namespace app {
class Foo { };
template <typename> class Bar { };
int foo(Foo const &) { return 99; }
template <typename T>
char foo(Bar<T> const &) { return 'x'; }
} // namespace app
The correct funtion foo is found by ADL so code like this works:
app::Foo foo;
app::Bar<void**> bar;
auto x = lib::libfunc(foo);
auto y = lib::libfunc(bar);
However, if I want to write a version of foo that works for types from the std-namespace, no matching function foo is found unless I place foo in the std-namespace which is not allowed:
#ifdef EVIL
namespace std {
#endif
template <typename T>
double foo(std::vector<T> const & ) { return 1.23; }
#ifdef EVIL
} // namespace std
#endif
std::vector<int> vec;
lib::libfunc(vec); // Only works if EVIL is defined
Is it possible to change the code so that a user can enable the functionality foo for a type without invading its namespace? I thought about partial template specializations of a class template in the lib-namespace but is there any other possibility?
I've found two solutions to this problem. Both have their downsides.
Declare All Std Overloads
Let overloads for standard types be found by normal lookup. This basically means declaring all of them before using the extension function. Remember: when you perform an unqualified call in a function template, normal lookup happens at the point of definition, while ADL happens at the point of instantiation. This means that normal lookup only finds overloads visible from where the template is written, whereas ADL finds stuff defined later on.
The upside of this approach is that nothing changes for the user when writing his own functions.
The downside is that you have to include the header of every standard type you want to provide an overload for, and provide that overload, in the header that just wants to define the extension point. This can mean a very heavy dependency.
Add Another Argument
The other option is to pass a second argument to the function. Here's how this works:
namespace your_stuff {
namespace adl {
struct tag {}
void extension_point() = delete; // this is just a dummy
}
template <typename T>
void use_extension_point(const T& t) {
using adl::extension_point;
extension_point(t, adl::tag{}); // this is the ADL call
}
template <typename T>
void needs_extension_point(const T& t) {
your_stuff::use_extension_point(t); // suppress ADL
}
}
Now you can, at basically any point in the program, provide overloads for std (or even global or built-in) types like this:
namespace your_stuff { namespace adl {
void extension_point(const std::string& s, tag) {
// do stuff here
}
void extension_point(int i, tag) {
// do stuff here
}
}}
The user can, for his own types, write overloads like this:
namespace user_stuff {
void extension_point(const user_type& u, your_stuff::adl::tag) {
// do stuff here
}
}
Upside: Works.
Downside: the user must add the your_stuff::adl::tag argument to his overloads. This will be probably seen as annoying boilerplate by many, and more importantly, can lead to the big puzzling "why doesn't it find my overload" problem when the user forgets to add the argument. On the other hand, the argument also clearly identifies the overloads as fulfilling a contract (being an extension point), which could be important when the next programmer comes along and renames the function to extensionPoint (to conform with naming conventions) and then freaks out when things don't compile anymore.

Simulating argument-dependent lookup for template arguments

I've encountered this problem while writing some library-like code recently, and I thought discussing it might help others as well.
Suppose I have a library with some function templates defined in a namespace. The function templates work on types supplied by client code, and their inner workings can be customized based on type traits defined for the client types. All client definitions are in other namespaces.
For the simplest example possible, a library function would basically have to look like this (note that all the code snippets are just wishful thinking, nothing compiles):
namespace lib
{
template<typename T> void f()
{
std::cout << traits_for<T>::str() << '\n'; //Use the traits in some way.
}
}
Client code would look like this:
namespace client
{
struct A { };
template<> std::string traits_for<A>::str() { return "trait value"; }
}
And then someone, somewhere could call
lib::f<client::A>();
and everything would magically work (the specialization of lib::f() would find the traits explicit specialization in the namespace where the template argument for T is declared, just like ADL does for functions and their arguments). The goal is to make it as easy as possible for client code to define those traits (there could be several) for each client class (there could be lots of those).
Let's see what we could do to make this work. The obvious thing is to define a traits class primary template in lib, and then explicitly specialize it for client types. But then clients can't define those explicit specializations in their own namespace; they have to exit it, at least up to the global namespace, define the explicit specialization, then re-enter the client namespace, which, for maximum fun, could be nested. I'd like to keep the trait definitions close to each client class definition, so this namespace juggling would have to be done near each class definition. Suddenly, a one-liner in client code has turned into a messy several-liner; not good.
To allow the traits to be defined in the client namespace, we could turn the traits class into a traits function, that could be called from lib like this:
traits_for(T())
but now we're creating an object of class T just to make ADL kick in. Such objects could be expensive to construct (or even impossible in some circumstances), so this isn't good either. We have to keep working with types only, not their instances.
Giving up and defining the traits as members of the client classes is not an option either.
Some plumbing required to make this work would be acceptable, as long as it doesn't complicate the definitions for each class and trait in the client namespace (write some code once, but not for every definition).
I've found a solution that satisfies these stringent requirements, and I'll write it up in an answer, but I'd like to find out what people think about this: alternatives, critique of my solution, comments about how all of this is either bleeding obvious or completely useless in practice, the works...
To find a declaration based on some argument, ADL looks like the most promising direction. So, we'll have to use something like
template<typename T> ??? traits_helper(T);
But we can't create objects of type T, so this function should only appear as an unevaluated operand; decltype springs to mind. Ideally, we shouldn't even assume anything about T's constructors, so std::declval could also be useful:
decltype(traits_helper(std::declval<T>()))
What could this do? Well, it could return the actual traits type if the helper would be declared like this:
template<typename T> traits_for<T> traits_helper(T);
We've just found a class template specialization in another namespace, based on the declaration of its argument.
EDIT: Based on a comment from Yakk, traits_helper() should take a T&&, to allow it to work if T's move constructor is not available (the function may not actually be called, but the semantic constraints required for calling it must be met). This is reflected in the complete sample below.
All put together in a standalone example, it looks like this:
#include <iostream>
#include <string>
#include <utility>
namespace lib
{
//Make the syntax nicer for library code.
template<typename T> using traits_for = decltype(traits_helper(std::declval<T>()));
template<typename T> void f()
{
std::cout << traits_for<T>::str() << '\n';
}
}
namespace client_1
{
//The following two lines are needed only once in every client namespace.
template<typename> struct traits_for { static std::string str(); };
template<typename T> traits_for<T> traits_helper(T&&); //No definition needed.
struct A { };
template<> std::string traits_for<A>::str() { return "trait value for client_1::A"; }
struct B { };
template<> std::string traits_for<B>::str() { return "trait value for client_1::B"; }
}
namespace client_2
{
//The following two lines are needed only once in every client namespace.
template<typename> struct traits_for { static std::string str(); };
template<typename T> traits_for<T> traits_helper(T&&); //No definition needed.
struct A { };
template<> std::string traits_for<A>::str() { return "trait value for client_2::A"; }
}
int main()
{
lib::f<client_1::A>(); //Prints 'trait value for client_1::A'.
lib::f<client_1::B>(); //Prints 'trait value for client_1::B'.
lib::f<client_2::A>(); //Prints 'trait value for client_2::A'.
}
Note that no objects of type T or traits_for<T> are created; the traits_helper specialization is never called - only its declaration is used.
What's wrong with just requiring clients to throw their specializations in the right namespace? If they want to use their own, they can:
namespace client
{
struct A { };
struct traits_for_A {
static std::string str() { return "trait value"; }
};
}
namespace lib
{
template <>
struct traits_for<client::A>
: client::traits_for_A
{ };
}
Could even give your users a macro if you don't want them to write all that out:
#define PROVIDE_TRAITS_FOR(cls, traits) \
namespace lib { \
template <> struct traits_for<cls> : traits { }; \
}
So the above can become
PROVIDE_TRAITS_FOR(client::A, client::traits_for_A)
ADL is awesome. Keep it simple:
namespace lib {
// helpers for client code:
template<class T>
struct default_traits{
using some_type=void;
};
struct no_traits{};
namespace details {
template<class T,class=void>
struct traits:lib::no_traits{};
template<class T>
struct traits<T,decltype(void(
traits_func((T*)0)
))>:decltype(
traits_func((T*)0)
){};
}
template<class T>
struct traits:details::traits<T>{};
}
Now simply add in the type Foo namespace:
namespace bob{
// use custom traits impl:
struct foo{};
struct foo_traits{
using some_type=int;
};
foo_traits traits_func(foo const*);
// use default traits impl:
struct bar {};
lib::default_traits<bar> traits_func(bar const*);
// use SFINAE test for any type `T`:
struct baz {};
template<class T>
std::enable_if_t<
std::is_base_of<T,baz>{},
lib::default_traits<T>
>
traits_func(T const*)
}
and we are done. Defining traits_func that takes a pointer convertable from foo* is enough to inject the trait.
If you fail to write such an overload, we get an empty traits, which is SFINAE friendly.
You can return lib::no_traits in an overload to explicitly turn off support, or just don;t write an overload that matches a type.