[cfe-dev] RFC: A mechanism for importing a declaration only when it exists

Richard Smith via cfe-dev cfe-dev at lists.llvm.org
Fri Jun 26 14:16:26 PDT 2020


On Fri, 26 Jun 2020 at 12:53, Louis Dionne via cfe-dev <
cfe-dev at lists.llvm.org> wrote:

> On Jun 26, 2020, at 12:22, Richard Smith <richard at metafoo.co.uk> wrote:
>
> On Thu, 25 Jun 2020, 21:05 Louis Dionne via cfe-dev, <
> cfe-dev at lists.llvm.org> wrote:
>
>> Hi,
>>
>> The C++ Standard Library has multiple headers of the form <cfoobar>,
>> which are
>> basically importing declarations from the corresponding <foobar.h> header
>> into
>> namespace std. For example, <cstdio> basically imports all the
>> declarations from
>> <stdio.h> into namespace std:
>>
>>     namespace std {
>>         using ::FILE;
>>         using ::fpos_t;
>>         using ::size_t;
>>         ...
>>     }
>>
>> When porting libc++ to new platforms, especially more embedded-ish
>> platforms,
>> it is very common that some declarations are not provided by the
>> underlying
>> C Standard Library. This leads to having to detect what platform we're on,
>> and then to conditionally exclude some declarations:
>>
>>     namespace std {
>>     #if !defined(SOME_WEIRD_PLATFORM)
>>         using ::FILE;
>>         using ::fpos_t;
>>     #endif
>>         using ::size_t;
>>     }
>>
>> Unfortunately, different platforms often offer slightly different
>> subsets, so
>> these #ifs quickly become difficult to maintain. Trying to find common
>> themes
>> for excluding declarations (e.g. #if !defined(_LIBCPP_HAS_NO_FILE)) is
>> vain,
>> because the subset provided by a platform is often arbitrary. For
>> example, I've
>> seen platforms where malloc() and free() were not provided, however
>> operator new
>> was -- so trying to carve out something like _LIBCPP_HAS_NO_ALLOCATION
>> wouldn't
>> really make sense.
>>
>> Given the difficulty of manually excluding such using declarations, I
>> came to
>> the conclusion that what we wanted was often something like (pseudocode):
>>
>>     namespace std {
>>     #if __has_declaration(::FILE)
>>         using ::FILE;
>>     #endif
>>
>>     #if __has_declaration(::fpos_t)
>>         using ::fpos_t;
>>     #endif
>>
>>     #if __has_declaration(::size_t)
>>         using ::size_t;
>>     #endif
>>
>>         ...
>>     }
>>
>> Basically, we want to import each declaration into namespace std only if
>> the
>> global namespace has such a declaration.
>>
>> Now, I understand this raises several questions. Of course, this couldn't
>> be
>> done at the preprocessor level because I don't think we can know whether a
>>
>> declaration exists at that time. However, I was curious to ask on this
>> list
>> whether someone could think of a reasonable way to solve this problem.
>>
>> Having some mechanism for doing this would be a huge life improvement for
>> libc++.
>>
>
> Do you always want to guard a namespace & scope using-declaration? We
> could probably support an attribute on such declarations that would result
> in them having no effect if no names are found -- or perhaps being invalid
> to reference if no names are found. (Otherwise, Clang already has some
> support for MSVC's __if_exists, but it's got some deep semantic problems,
> and I don't think we should recommend its use.)
>
>
> Yes, as far as I can tell, I only want to apply this to using declarations
> inside namespace std. It's a pretty constrained use case.
>
> I think the better semantics would be to have no effect if no names are
> found, since that's what's closest in effect to having an #ifdef in front
> that removes it.
>

That semantic model may be a better fit for your use case, but it's not
really a great fit for C++ more broadly. Consider a case such as:

template<typename T> int f() {
  [[clang::its_ok_if_this_doesnt_exist]] using T::x;
  return x;
}

During the phase 1 name lookup for 'x' in the return-statement, we find the
local (but dependent) using-declaration and bind to that. During
template instantiation, it would be challenging to pretend that that 'x'
doesn't exist and fall back on an enclosing name 'x'. The semantic model
that fits better for this case would be to say that name lookup stops at
the using-declaration (and doesn't consider enclosing scopes), but then the
result is an error because the name 'x' didn't resolve to anything.
(Roughly as if a failed using-declaration with the attribute in question
behaves as if it were =delete'd or 'requires false'd in some sense.)

If we're going to add an extension for this, I'd prefer that it's something
that can be applied soundly in general, rather than introducing another
situation like the one for `__if_exists` where we support it only partially
and only in certain contexts.

It's not clear to me whether this approach would actually be bad for your
use case, or merely different from what you get with #ifdefs. An error on
use of `std::fopen` that says "can't refer to std::fopen because there is
no declaration named 'fopen' in the global namespace" or similar seems like
it might actually result in a better user experience than one that says "no
name 'fopen' in namespace 'std'" or similar.

The resulting error is also quite straightforward, i.e. the compiler just
> says "oops, no such member in namespace std". We could potentially augment
> the diagnostic to say "there's no such member in namespace std BECAUSE we
> tried importing it and it wasn't in the global namespace".
>
> Louis
>
>
> Cheers,
>> Louis
>>
>> _______________________________________________
>> cfe-dev mailing list
>> cfe-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-
>> <https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev>
>
>
> _______________________________________________
> cfe-dev mailing list
> cfe-dev at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200626/f23d8959/attachment-0001.html>


More information about the cfe-dev mailing list