1

I have a function like so:

template <typename T>
void modify(T& x) {
  ...
}

I would like to add an overload specialized for the type Foo, like so:

void modify(Foo& x) {
  ...
}

However, this poses the problem that this definition will fail to compile if the type Foo doesn't exist, but I'd like this header to still compile in that case, just with that overload (logically) omitted. After all, the Foo overload won't be useful if the user hasn't included a definition of that type.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • Does your application allow the type to be registered somehow? Then it becomes an easy task. Otherwise I don't think it's doable. – davidhigh Jun 09 '21 at 22:13
  • 2
    Does the header file that defines `__m256i` #define something (maybe an include guard)? If so, you could `#ifdef` (gasp) off that. Might not be very portable though, – Paul Sanders Jun 09 '21 at 22:15
  • @PaulSanders - let's say it doesn't. I don't want to check any macro anyway because this would then depend on the include order: if this function was in a header included before the one that defines `__m256i` this would fail to include the overload. – BeeOnRope Jun 09 '21 at 22:26
  • @davidhigh - it seems doable with SFINAE: we can exclude the overload if the `__m256i` type does not exist. The problem is then that the two overloads (the original and `__m256i` specific one might then both be equally suitable resulting in an ambiguity. – BeeOnRope Jun 09 '21 at 22:27
  • 1
    _if this function was in a header included before the one that defines __m256i this would fail to include the overload_ If you want the overload to compile / exist then the header file defining `__m256i` must be included first, no? – Paul Sanders Jun 09 '21 at 22:29
  • @PaulSanders - I dislike the idea, yes, because there is no _reliable_ define like that, and it's not a generic solution: I would need to look for a suitable define for every type, and if the type was unknown it wouldn't work. Yes, the function as written would fail if the include order was wrong: part of the trick is to fix this: to defer the type lookup until instantiation. I believe this needs SFINAE or similar. The point of this approach is to avoid dragging in a giant header that 95% of users won't care about (and some won't even have), but to support that type for the other 5%. – BeeOnRope Jun 09 '21 at 22:44
  • @BeeOnRope Even if you could SFINAE your way there, you would invariably have to declare something called `__m256i` in the process, which is illegal by virtue of starting with 2 underscore. So It's going to get pretty ugly anyways if you want to go there, –  Jun 09 '21 at 22:52
  • @Frank - perhaps `__m256i` was a bad example, my question really applies to any type. I'll change it to `Foo` to avoid this double underscore issue which I don't think is core to the issue. – BeeOnRope Jun 09 '21 at 22:59
  • @Frank - that said, I'm surprised I would have to declare something called `__m256i` in any case? I would expect to use something like "if exists __m256i, include this overload" (where I'll be using the existing definition) and "if not, don't include it" (where I won't have to refer to __m256i). – BeeOnRope Jun 09 '21 at 23:01
  • 3
    C++ has no 'if type xxx exists' construct, unfortunately. It would need compiler support and it just isn't there. – Paul Sanders Jun 09 '21 at 23:05
  • "`if exists __m256i`": the problem with that is that `__m256i` needs to refer to something in order to make that comparison. You unfortunately can't compare against a name by itself, it has to be a name that refers to something. –  Jun 09 '21 at 23:08
  • @PaulSanders - what about something [like this](https://stackoverflow.com/a/26876264/149138) or [this](https://devblogs.microsoft.com/oldnewthing/20190711-00/?p=102682)? – BeeOnRope Jun 09 '21 at 23:16
  • @BeeOnRope Both of these rely on the type being testing being a type that can be incomplete, which is not the case here. –  Jun 09 '21 at 23:25
  • 1
    [this approach](https://stackoverflow.com/questions/10711952/how-to-detect-existence-of-a-class-using-sfinae/26876264#26876264) problem is you cannot actually use `A` afterwards, only `::A`: http://coliru.stacked-crooked.com/a/fd8ebac5cb93a599 – dewaffled Jun 09 '21 at 23:38
  • Whey are you trying to overload `method(Foo &)` for a non-existant `Foo`?. In any event, a *declaration* (not a definition) of `Foo` is needed to declare such a `method()`. The definition of `method()`, if it doesn't use its argument, does not require `Foo` to be defined either. But, if `method()` uses the passed `Foo()`, `Foo` must be defined. Also, to call such a function, the caller must be able to instantiate `Foo` in order to pass an object by reference (or to initialise a reference, since creating a reference that does not refer to an object is a diagnosable error). – Peter Jun 09 '21 at 23:39
  • @Peter - basically `Foo` is declared in a 100,000-line header file. I don't want to include this header in the standalone utility header that declares `modify` if the user will never use the `Foo` overload. However, if the user _does_ already include that header themselves, they should be able to use the overload I provide. So the caller will not have any problem instantiating with `Foo`. The body of `modify` is generic, it does not really depend on details of `Foo`. The `modify` overload will probably have to be a template, which sort of solves the concerns in the second half of your comment. – BeeOnRope Jun 09 '21 at 23:45
  • That is, if gets instantiated in the first place (by a call passing a `Foo&`), there will be a declaration of `Foo` in scope so compiling the function will proceed w/o issue. – BeeOnRope Jun 09 '21 at 23:45
  • If you can somehow get compile-time constant `Foo_is_defined` you could at least just use `void modify(T& x) { if constexpr(Foo_is_defined) { if constexpr (std::is_same_v) { ... /* T is Foo actions */ return; } } ... /* T is not Foo actions */ }` without additional overload needed but I doubt you can even get `Foo_is_defined` – dewaffled Jun 09 '21 at 23:46

1 Answers1

0

A non-portable hackish solution is to use ifdefs checking for the Foo header include guard (which might change, or it might be using #pragma once). Also this requires the Foo header be included before your header in a translation unit.

template <typename T>
void modify(T& x) {
  ...
}

#ifdef FOO_H
template<>
void modify(Foo &x) {
  ...
}
#endif

Another cleaner option if you have the option to use source files, is to forward declare Foo in your header, then in the modify function, call another function defined in a source file which has the full knowledge of Foo:

// your header
template<typename T>
void modify(T &a) {
    ...
}

class Foo;

void modify_foo(Foo &a);

template<>
void modify(Foo &a) {
    modify_foo(a);
}
// some source file
#include "foo.h"

void modify_foo(Foo &a) {
    ...
}
mo_al_
  • 561
  • 4
  • 9