4

I'm trying to use a concept that applies to both built-in and user-defined types. In my setting, it is inconvenient to forward-declare certain functions (because the functions depend indirectly on the concepts causing my problem).

My problem is that concepts don't recognize functions that were not previously declared if those functions have arguments of built-in type. Here's a minimal non-working example:

struct S {};

// void func(bool);  // Works if this line uncommented

template<typename T> concept has_func =
  requires(T t) { func(t); };

void func(S) {}
static_assert(has_func<S>); // OK

void func(bool) {}
static_assert(has_func<bool>); // Error

My first question: Why exactly this is happening? I suspect it has to do with ADL, because even though everything is in the global namespace, the global namespace is still a namespace, and bool, being built-in, is not technically in that namespace. So somehow I need to express the concept in terms of some user-defined type that captures the behavior of bool.

My second question: What is a good workaround to defer name lookup on my function? My fallback (since I can't forward-declare in my actual code) is to redefine the concept as std::same_as<T, bool> || .... However, I'm wondering if there's a more general hack to defer name lookup to the point where the concept is used. I've tried things like using requires(std::type_identity_t<T> t), but it doesn't help.

update:

I don't have a hack to work around the problem, but I understand that it is being caused by the dependent name lookup rules. Specifically:

  • non-ADL lookup examines function declarations with external linkage that are visible from the template definition context
  • ADL examines function declarations with external linkage that are visible from either the template definition context or the template instantiation context

So the two things I've tried are:

  1. Calling some other function dofunc with an extra argument of a type that triggers ADL. That sort of works, except this function needs to call func, so the placement of the dofunc function now becomes tricky for func to be in scope. It doesn't really improve things.

  2. Using some kind of proxy type with an operator bool, but this doesn't work because it doesn't trigger ADL of the function I want to call.

It may be that what I want to do is impossible on purpose, to avoid situations where the concept means something different in different contexts. Of course, with ADL the concept still might be different in different contexts, depending on what is in scope at the time the concept is used, so I still don't really get the justification for this restriction.

user3188445
  • 4,062
  • 16
  • 26
  • @NicolBolas I have a solution, it's just to add `std::same_as ||` into my concept. The recursion isn't a problem, because it bottoms out pretty quickly, so there's not really a _recursion problem_ more like a non-ADL problem. – user3188445 Jun 16 '21 at 18:25
  • wouldn't adding `std::same_as ||` to the concept be exactly as annoying to do as forward declaring `void func (bool)`? –  Jun 16 '21 at 18:26
  • @Frank no, because there isn't a `foo(bool)` function to forward declare. There's a `foo(some_other_concept auto t)` function that happens to accept a bool. – user3188445 Jun 16 '21 at 18:28
  • @Frank Barry's [answer](https://stackoverflow.com/a/56741596/4944425) in the Q&A you linked [may work](https://godbolt.org/z/eTPhEsora), but it's kind of ugly. – Bob__ Jun 16 '21 at 18:31
  • @Bob__ At that point why not just put the `same_as` in the concept? There's only a finite number of built-in types, so might as well just enumerate them all in the concept rather than enumerating them in some other class. I was just hoping you could do something like cast the bool to something that is equivalent to bool but triggers ADL. – user3188445 Jun 16 '21 at 18:37
  • @user3188445 The fact that `func(some_other_concept auto t)` exists does not prevent you from also forward declaring `func(bool)` as an overload. For example: https://gcc.godbolt.org/z/Y6fodeehE (this doesn't setup the chained dependency, but it does show that the overloaded forward declaraion works) –  Jun 16 '21 at 18:37
  • @Frank But if you ODR-used func(bool), the program wouldn't link. – user3188445 Jun 16 '21 at 18:41
  • Clang 12 seems to work ... https://godbolt.org/z/E4TYnsxPv but unsure if I modified your example incorrectly – Öö Tiib Jun 16 '21 at 19:06
  • No. You defined a function `void func(bool) {}`. Of course if you have such a function defined, you can declare it. In my code, I don't have such a function. I only have `void func(some_other_concept auto t)`. So declaring a function `void func(bool)` that is never defined will prevent the program from linking, because calls to, say, `func(true)` won't get resolved to the function that exists, namely `func(some_other_concept auto t)`. – user3188445 Jun 16 '21 at 19:54

1 Answers1

0

Yes, static_assert(has_func<bool>) in your initial code does not work, because bool is a build-in type, so no argument-dependent lookup (ADL) is performed and func(bool) must be visible by the concept definition.

I think you can utilize a proxy type A<T> and ADL to make a similar concept accepting build-in types:

template<class T>
struct A{
    operator T() const;
};

template<typename T> concept has_func =
  requires { func(A<T>{}); };

// Some tests:
void func(bool&) {}
static_assert(has_func<bool&>);
static_assert(!has_func<int>); // func(1) is not valid

void func(bool) {}
static_assert(has_func<bool>);

struct S{};
void func(S) {}
static_assert(has_func<S>);

Demo: https://gcc.godbolt.org/z/bGY8zGes9

Fedor
  • 17,146
  • 13
  • 40
  • 131