[std-proposals] Templates usage with *this

93 views
Skip to first unread message

artyom....@gmail.com

unread,
Aug 31, 2013, 4:25:54 AM8/31/13
to std-pr...@isocpp.org

Hi,

This in not a well-formed proposal but just my thoughts about the subject feature. May be something similar is already planned but I did not find it in C++14 features list.


So now, especially after introducing “N2439 - Extending move semantics to *this”, (*this) can be matched to differently qualified type:


class A {

    void Method(); // 1. *this is "A&"

    void Method() const; // 2. *this is "const A&"

    void Method() volatile; // 3. *this is "volatile A&"

    void Method() const volatile; // 4. *this is "const volatile A&"

    void Method() &&; //5. matched to "A &&" but in scope of method implementation *this is "A&" since it has name

    void Method() const &&; //6. This one and similar CV+rvalue_ref, may be not so meaningful in most cases but still worth to mention. *this is "const A&", probably always matches "A&&".

};


Currently there is no way to create a method template, having type of “*this” as template parameter. In some cases you might want to have generic implementation for a method and properly forward “*this” to some other function. The number of combinations for *this types reminds me the situation with possible qualifiers for arguments in templates before perfect forwarding pattern was introduced. Fortunately, now we have only one problematic argument with dozen of choices (which were doubled after N2439).


Small real world example of the applicable situation. Let's say I have a class which encapsulates some value. The value has dynamic type, and may have underlying type which requires heap allocation (e.g. string) so it is perfect for moving. I have method Get<TargetType>() which returns the value. The trick with the Get() method is that for “rvalue *this” we can move the stored value to the result returned to the caller (and this is very cool language feature indeed).


class Value {


    template <typename T>

    T

    Get() const &

    {

        // return a copy of the stored value

    }


    template <typename T>

    T

    Get() &&

    {

        // move the stored value to the returned result

    }


    // And here we might want to have some aliases

    template <typename T>

    operator T() const &

    {

        return Get<T>();

    }


    template <typename T>

    operator T() &&

    {

        return std::move(*this).Get<T>();

    }


    template <typename T>

    T

    GetWithStuff() const &

    {

        return Get<T>() + Stuff<T>();

    }


    template <typename T>

    T

    GetWithStuff() &&

    {

        return std::move(*this).Get<T>() + Stuff<T>();

    }

};


The problem is that Get() method has some aliases (it might be another method with just different name or some generalized additional logic, or, for example, type casting operator). Did you also noticed copy-pasting which naturally seems must be avoided? What I basically might want from the language, is the ability to perfectly forward “*this” to some functions. Possible syntax (just the first idea):


template <this ThisType, typename T>

T

GetWithStuff() ThisType && //roughly equivalent to "T&&" in arguments when using arguments forwarding

{

    return std::forward<ThisType>(*this).Get<T>() + Stuff<T>();

}


I think “this” keyword can be used in template parameters which is very natural. Also I feel that all rules applicable to type deduction of argument in “T &&” form (in perfect forwarding pattern context), as well as all rules for argument types and templates there, should be the same for “this” argument which is just written next to parenthesis. Thus such construction


template <this ThisType, typename T>

T

GetWithStuff() const ThisType &

{

    return std::forward<ThisType>(*this).Get<T>() + Stuff<T>();

}


will not have much sense (ThisType will be just “Value”) but still should work to make things consistent.


May be having template parameter name in “*this” specification part is redundant:

template <this ThisType, typename T>

T

GetWithStuff() const & //presence of "this" in template parameters automatically applies it there

{

    return std::forward<ThisType>(*this).Get<T>() + Stuff<T>();

}


Obviously “this” can be used in template parameters for non-static class methods only.


IMHO such feature would be a nice addition for making the language even more beautiful, and it should not be a problem to implement in compilers since “this” argument already should be implicitly present (with type information) in most places, which are subject for the changes.


Best regards,

Artyom

Maurice Bos

unread,
Aug 31, 2013, 11:18:22 AM8/31/13
to std-pr...@isocpp.org
Hello,

I've been thinking about the same problem a few weeks ago. I was writing a blog post about 'rvalue references for *this' (aka &&-member functions), in which I also complained a bit about how annoying it is to define an accessor properly (&, const &, &&, const &&, etc.) (http://blog.m-ou.se/2013/07/21/rvalue-references-for-this.html). The suggestion I gave had to 'fix' this, is similar to yours:

class foo {
    std::string name_;
public:
    decltype(auto) name() T && { return std::forward<T>(*this).name_; }
}:

However, there's one big difference with your solution: Here, foo::name() is not a template. This syntax just and defines 8 different versions of foo::name (the &, const &, volatile &, const volatile &, and all the && versions).

I'm not sure if it'd be better or not to make it a template (I haven't put very much thought into it yet), but I just wanted to mention this option.

Also, there needs to be some way to specify you only want to define all the & versions but not the && versions, or only the const versions. Something like:

void whatever() T & { ... }
void whatever() T const { ... }

This would make repeating the template paramaeter name after the argument list non-redundant in your template idea.

-Maurice-




--
 
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Artyom Lebedev

unread,
Aug 31, 2013, 1:33:33 PM8/31/13
to std-pr...@isocpp.org
I'm not sure if it'd be better or not to make it a template (I haven't put very much thought into it yet), but I just wanted to mention this option.
I definitely sure that it must be a template. Basically we have a function template which has "this" implicit argument type as a template parameter, i.e. depending on the provided "this" argument type different function code is instantiated - this is true for this case and it is how templates work. Also examples below in this message will show, how it is close to usual templatized argument.


Also, there needs to be some way to specify you only want to define all the & versions but not the && versions, or only the const versions. Something like:

void whatever() T & { ... }
void whatever() T const { ... }

This would make repeating the template paramaeter name after the argument list non-redundant in your template idea.

Think about it as it would be a regular function, for example, with such definition:
template <class ThisType>
void
Method(ThisType &&thisRef)
{
// thisRef is *this
}

which could have such equivalent:
template <this ThisType>
void
Method() &&
{
}

As I have written before, all rules for types matching and deduction for arguments should work for this case. In such case, this example would match both "const ThisType &" (ThisType deduced to "const ClassType &" and "ThisType &&" (ThisType deduced to "ClassType") , and "ThisType &" (ThisType deduced to "ClassType &") - in the way it is described in std::forward example: http://en.cppreference.com/w/cpp/utility/forward. So, in other example:
template <this ThisType>
void
Method() & //think it is Method(ThisType &)
{
}

the method will match only "[const and/or volatile] ThisType &" and "ThisType &" (deducing to the "const and/or volatile ClassType" and "ClassType"), not "ThisType &&" (which should produce the same error, e.g. in GCC - "invalid initialization of non-const reference of type ‘... &’ from an rvalue of type ..."), just like it would be if it was written as argument:
template <class ThisType>
void
Method(ThisType &thisRef)
{
// thisRef is *this
}


So what I want to say, is that CV-qualifiers and lvalue/rvalue reference after parenthesis is just a syntactic sugar for the case if "this" argument was explicit and was written inside parenthesis and with the name. And still seems that the name is not needed there because we always know what and where it is. Correct me if I'm wrong.

Maurice Bos:

Maurice Bos

unread,
Aug 31, 2013, 2:01:06 PM8/31/13
to std-pr...@isocpp.org

2013/8/31 Artyom Lebedev <artyom....@gmail.com>

I'm not sure if it'd be better or not to make it a template (I haven't put very much thought into it yet), but I just wanted to mention this option.
I definitely sure that it must be a template. Basically we have a function template which has "this" implicit argument type as a template parameter, i.e. depending on the provided "this" argument type different function code is instantiated - this is true for this case and it is how templates work. Also examples below in this message will show, how it is close to usual templatized argument.

Yes, I agree a template is better.
 

Also, there needs to be some way to specify you only want to define all the & versions but not the && versions, or only the const versions. Something like:

void whatever() T & { ... }
void whatever() T const { ... }

This would make repeating the template paramaeter name after the argument list non-redundant in your template idea.

(snip)


So what I want to say, is that CV-qualifiers and lvalue/rvalue reference after parenthesis is just a syntactic sugar for the case if "this" argument was explicit and was written inside parenthesis and with the name. And still seems that the name is not needed there because we always know what and where it is. Correct me if I'm wrong.

Ah yes, I see.

David Krauss

unread,
Aug 31, 2013, 10:26:42 PM8/31/13
to std-pr...@isocpp.org
I must admit I didn't read your entire post.

You can't get the actual type of the argument which initialized *this; as noted only templates can represent simultaneous types and otherwise you have all the combinations of cv-qualification and value category.

What you can do is forgo a member function and use ADL and a non-member function template with perfect forwarding regulated by SFINAE. This gets you not only cv-qualification and value category but also the static type from the call site.

struct s {
   
int x;

   
template< typename self >
   
friend
   
typename std::enable_if< std::is_base_of< s, typename std::decay< self >::type >::value,
        std
::decltype( std::declval< self >().x ) >::type
    get_x
( self && o )
       
{ return std::forward< self >( o ).x; }
};

If I were you, though, I'd just elaborate the accessor functions as before and delegate from the rvalue overload to the lvalue overload with an explicit move. As you mentioned, const rvalue isn't actually used for anything, so the const overload need not be ref-qualified. So the new change doesn't effectively double the complexity, but in usual practice only one new case is added, which essentially just forwards to an existing case.

(Standard disclaimer: accessor functions are evil. C++ has a perfectly good model for accessing members; do not roll your own unless you legitimately have a custom object model. It looks like OP here might.)

Artyom Lebedev

unread,
Sep 1, 2013, 3:23:44 AM9/1/13
to std-pr...@isocpp.org
Standard disclaimer: accessor functions are evil. C++ has a perfectly good model for accessing members; do not roll your own unless you legitimately have a custom object model.
Probably you a bit misunderstood the idea. It by no means related to accessor functions. May be you were confused by my first example with Get() method name and Maurice example provided by link where he returns data member in his method. So I will provide another example for better understanding of use cases.

Let's say we have a class which represents some path in a filesystem (or in any hierarchical data structure). Path is stored as its components (strings between separators) list. It might be std::list<std::string> or std::vector<std::string>. The class has two versions of Slice() method which returns arbitrary part of the path defined by start components index and number of components (like substr()). The version for "*this" = "const ClassType &" creates a new list with copies of necessary component strings. The version for "*this" = "ClassType &&"can be optimized a lot - it does not allocate or copy anything, instead it trims existing list as necessary and moves it to the returned result. So far it is well done with existing functionality. Now let's say you also have methods DirName() and BaseName() which should return path part before the last component and last component only. Obviously, they can use slice method, but because of the subject feature lack, you will need to define several versions for them with the same algorithm inside - the typical use case for templates. The code:

class Path {
private:
    std::vector<std::string> components;

    Path(std::vector<std::string> &&components):
        components(components)
    {}
public:
    //version for lvalue
    Path
    Slice(size_t start, size_t count) const &
    {
        //create new components list
        std::vector<std::string> slice;
        //populate it with necessary components
        ...
        return Path(std::move(slice));
    }

    //version for rvalue
    Path
    Slice(size_t start, size_t count) &&
    {
        //just trim unnecessary components from own list
        if (start != 0) {
            components.erase(components.begin(), components.begin() + start);
        }
        components.resize(count);
        //return this object as the result
        return std::move(*this);
    }

    //This is full of copy-paste current approach
    Path
    BaseName() const &
    {
        if (components.empty()) {
            return Path();
        }
        return Slice(components.size() - 1, components.size());
    }

    Path
    BaseName() &&
    {
        if (components.empty()) {
            return std::move(*this);
        }
        return std::move(*this).Slice(components.size() - 1, components.size());
    }

    Path
    DirName() const &
    {
        if (components.empty()) {
            return Path();
        }
        return Slice(0, components.size() - 1);
    }

    Path
    DirName() &&
    {
        if (components.empty()) {
            return std::move(*this);
        }
        return std::move(*this).Slice(0, components.size() - 1);
    }

    //And this the proposed one
    template <this PathType>
    PathType
    BaseName() &&
    {
        if (components.empty()) {
            return std::forward<PathType>(*this);
        }
        return std::forward<PathType>(*this).Slice(components.size() - 1, components.size());
    }

    template <this PathType>
    Path
    DirName() &&
    {
        if (components.empty()) {
            return std::forward<PathType>(*this);
        }
        return std::forward<PathType>(*this).Slice(0, components.size() - 1);
    }
};


I hope this example is much more clear. In general, the idea is that now we have full set of qualifiers for "*this", just like for any other argument, but lack possibility to make its type templatized, like any other argument. In my opinion, it would be very logically, and now it looks like unfinished solution. And, as illustrated by the examples, there are real use cases for that.


You can't get the actual type of the argument which initialized *this; as noted only templates can represent simultaneous types and otherwise you have all the combinations of cv-qualification and value category.
Yes, I fully understand this, and as I described in previous messages, we are interested only in proper match logic (e.g. lvalues not matched to rvalue ref) and deduced template type - just like with regular arguments and perfect forwarding pattern.


As you mentioned, const rvalue isn't actually used for anything, so the const overload need not be ref-qualified.
 Const qualifier is a bit special, in the meaning that rvalue is matched to "const T &". But do not forget about "volatile", there also might be use cases for it.

David Krauss:

Maurice Bos

unread,
Sep 1, 2013, 5:51:25 AM9/1/13
to std-pr...@isocpp.org
Two other examples to consider are implementations of optional<T> and variant<T...>. *optional, optional.get(), variant.get_as<T>() etc. should return the same kind of reference with the same cv-qualifiers as the optional/variant itself. (So give_me_an_optional_string().get() should give std::string&&.)


2013/9/1 Artyom Lebedev <artyom....@gmail.com>

--

David Krauss

unread,
Sep 2, 2013, 1:55:54 AM9/2/13
to std-pr...@isocpp.org


On Sunday, September 1, 2013 3:23:44 PM UTC+8, Artyom Lebedev wrote:
Standard disclaimer: accessor functions are evil. C++ has a perfectly good model for accessing members; do not roll your own unless you legitimately have a custom object model.
Probably you a bit misunderstood the idea. It by no means related to accessor functions.

I don't object to accessors unless they're gratuitous. There are use cases and hence this is the disclaimer is an afterthought. Maurice mentions variant above.

But do note that there is never an actual need to repeat the algorithm in the various overloads, as my example demonstrates. If the friend function is an unpalatable interface, the member overloads to call it will always be only one line each.

Artyom Lebedev

unread,
Sep 2, 2013, 5:30:37 AM9/2/13
to std-pr...@isocpp.org
If the friend function is an unpalatable interface, the member overloads to call it will always be only one line each.
Yes, I believe there are workarounds (you have demonstrated one). However, as every workaround (in contrast with native feature) they are usually not so nice, and may be cumbersome. What I want, is to introduce a feature which would make all the mentioned examples simple, short, nice and readable/understandable. In real world big projects, one of the most important requirement for the code is readability and therefore maintainability. Often this is the reason, why C++ is dropped away from the options of possible project implementation languages - not each and every average programmer will quickly understand what is done by dozen of nested type-manipulating templates in your example. And searching for a bug in such construction, especially if you do not know it is there, is a real pain (e.g. due to improper types manipulating, reference is passed and stored somewhere instead of a copy, leading to invalid memory access later, when the reference is not valid anymore). So I do not see any reason to discuss workarounds, unless one is really simple and does not introduce any trade-off between functionality and code simplicity/readability.

This is your example. I modified it to return reference to stored value (for example if it was some [] operator which usually returns a reference) and as you recommended added member functions to hide friend function. Yes, they are one-liners and do not contain alghorithms but scale of the disaster is still visible:

struct S {
    std::string x;

    //replace (decltype(std::declval<Self>().x) &) to some more advanced
    //construction, so that lvalue and rvalue reference would properly created.
    //I do not even want to do it.
    template<typename Self>
    friend
    typename std::enable_if<std::is_base_of<S, typename std::decay<Self>::type>::value,
                            decltype(std::declval<Self>().x) &>::type
    _get_x(Self &&self)
    { return std::forward<Self>(self).x; }

    std::string &
    Get_x() &
    {
        return _get_x(*this);
    }

    const std::string &
    Get_x() const &
    {
        return _get_x(*this);
    }

    volatile std::string &
    Get_x() const &
    {
        return _get_x(*this);
    }

    const volatile std::string &
    Get_x() const volatile &
    {
        return _get_x(*this);
    }

    std::string &&
    Get_x() &&
    {
        return _get_x(std::move(*this));
    }

    std::string const &&
    Get_x() const &&
    {
        return _get_x(std::move(*this));
    }

    std::string volatile &&
    Get_x() volatile &&
    {
        return _get_x(std::move(*this));
    }

    std::string const volatile &&
    Get_x() const volatile &&
    {
        return _get_x(std::move(*this));
    }
};
And here is a replacement with the new feature:
struct S {
    std::string x;

    template <this Self>
    auto
    Get_x() && -> decltype(std::forward<decltype(std::declval<Self>().x)>(std::declval<Self>().x))
    {
        return std::forward<decltype(std::declval<Self>().x)>(std::declval<Self>().x);
    }
};
The templates there are still long enough, but the example is less real than I previously mentioned. I also repeat the other my thought - in general, the idea is that now we have full set of qualifiers for "*this", just like for any other argument, but lack possibility to make its type templatized, like any other argument. In my opinion, it would be very logically, and now it looks like unfinished solution. So this is ideological question.
--
 
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/xMlthAq8DWk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

David Krauss

unread,
Sep 2, 2013, 8:14:03 AM9/2/13
to std-pr...@isocpp.org


On Monday, September 2, 2013 5:30:37 PM UTC+8, Artyom Lebedev wrote:

struct S {
    std::string x;

    //replace (decltype(std::declval<Self>().x) &) to some more advanced
    //construction, so that lvalue and rvalue reference would properly created.
No, adding the & undermines the preceding decltype. You might as well write int &. And that bit of type arithmetic is common to your example, in which it's similarly noisy but expressed slightly differently. Anyway we can both use deduced return type. And unless this is a public interface with a name reused by another interface, there is actually no need for SFINAE, either. So it starts to look a lot more like your example.
    template<typename Self>
    friend
    decltype( auto )

    _get_x(Self &&self)
    { return std::forward<Self>(self).x; }

    std::string &
    Get_x() &
    {
        return _get_x(*this);
    }

Recall that const && and non-const && have same semantics. You don't need to ref-qualify the const case.
    const std::string &
    Get_x() const
    {
        return _get_x(*this);
    }
Are you supplying the volatile overloads just because the volatile qualifier exists? You must realize it is only used for device drivers, and applying it to a high-level object cannot have useful effects. So, cutting those…
    std::string &&
    Get_x() &&
    {
        return _get_x(std::move(*this));
    }

};
See? That wasn't so bad.

Ultimately the motivation for your case is providing the complete set of 4 cv-qualifications x 2 ref-qualifications, but in practice only 3 count. If you are so obsessed, or really intend to write high-level template code over volatile storage (where each memory access is significant so you can't touch anything accidentally), then copy-paste-tweaking 8 one-liners will be your just deserts.

Reply all
Reply to author
Forward
0 new messages