简体   繁体   中英

Perfect forwarding without ODR-use

Consider the following snippet, as if written in a header file:

struct Foo {
    // ...
};

template <class... Args>
Foo makeFoo(Args &&... args) {
    return {std::forward<Args>(args)...};
}

I can call makeFoo with some parameters, and get back a Foo . Great.

Now what I want to do is replace some of makeFoo 's arguments with tags, which look just like this (still in the header file):

constexpr struct tag_type { using type = ActualType; } tag;

These tags should be detected inside makeFoo and substituted for actual objects before calling Foo 's constructor. So the call would look like:

auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);

But here's the catch: this way of declaring my tags is super convenient, but if I ODR-use any of them, I need to provide a definition somewhere else. Not convenient at all.

And according to this conveniently specific answer (emphasis mine):

"the object isn't odr-used" is probably the only questionable condition. Basically, it requires that you don't necessitate the variables runtime existence as a symbol, which in turn implies that

  • You don't bind it to a reference (=> you don't forward it!)
  • [...]

... I'm in a pickle indeed.

How can I sift out the tags from the arguments without ODR-using them, while perfect-forwarding the other arguments?

  • A "tag" is defined as something that has a type typedef.

  • Each tag declaration is generated by a macro defineTag(name, ActualType) , so changing that is fine as long as it is self-contained and doesn't alter (too much) the syntax of the call to makeFoo .

  • An alternate solution that isn't concerned with ODR at all is fine.

  • C++17's inline variables sound like salvation, but I'd like to avoid locking myself into bleeding-edge compilers on this project for that single issue.

If you can change the way you obtain type , you can avoid changing your makeFoo invocation by using enumeration constants, which evaluate as prvalues of distinct types:

template <typename> struct tag_helper;

enum { tagBar }; template <> struct tag_helper<decltype(tagBar)> { using type = Bar; };
enum { tagBaz }; template <> struct tag_helper<decltype(tagBaz)> { using type = Baz; };

auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);

How about a constexpr function to generate the tags?

constexpr struct tag_type { using type = ActualType; };

constexpr tag_type tag() { return {}; }

Usage:

auto myFoo = makeFoo("hi", ::tagBar(), 42, ::tagBaz())

Alternatively, you can get construct the tags from their types in-place instead of using a function:

constexpr struct tag { using type = ActualType; };

Usage:

auto myFoo = makeFoo("hi", ::tagBar{}, 42, ::tagBaz{})

You could make your tags variable templates?

template <class T>
struct tag_t { using type = T; };

template <class T>
constexpr tag_t<T> tag{};

Which you would use as:

auto myFoo = makeFoo("hi", ::tag<Bar>, 42, ::tag<Baz>);

Isn't the normal way to do this simply to declare the constexpr tag definitions static?

Presumably Foo's constructor is only interested in the type, so the presence of multiple models of the tag type is unimportant.

#include <utility>

struct Foo {
    template<class...Args>
            Foo(Args&&...args)
    {}

    // ...
};

template <class... Args>
Foo makeFoo(Args &&... args) {
    return {std::forward<Args>(args)...};
}

struct Bar {};
struct Baz {};

static constexpr struct tagBar_type { using type = Bar; } tagBar {};
static constexpr struct tagBaz_type { using type = Baz; } tagBaz {};

int main()
{
    auto myFoo = makeFoo("hi", ::tagBar, 42, ::tagBaz);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM