C++幼女先輩

プログラミング成分多め

【UNITY β機能】ScriptableRendererLoop 使ってみる

ScriptableRendererLoopとは

Unity 2017から搭載される予定の機能で現在はβバージョンである 今までブラックボックスだったレンダリングのループをある程度カスタマイズできる

カリング、Depthクリア、不透明描画、半透明描画・・・ 等の処理を変更できる サンプルによると HDRやGIや色々と高度な処理ができるようである

まずはお試し

UNITYβ版ダウンロード

この機能を試すにはβ版が必要だ

Unity - Beta から ダウンロードしよう。

過去バージョンを取得する事も可能だが私は Unity 2017.1.0b8 を使った。

ScriptableRendererLoopサンプル ダウンロード

https://github.com/Unity-Technologies/ScriptableRenderLoop/tree/unity-2017.1b5 ここにサンプルがある。 TagでUNITYのバージョンを選びダウンロードするが、エディタを2017.1.0b5にしても同じビルドエラーだった

ビルドしたらエラーが出るので 今回は無慈悲にエラーをコメントアウトした おそらく Experimental.PostProcess のファイルが欠乏しているだけとおもわれるが

HDRRenderPipeline.csの using UnityEngine.Experimental.PostProcessing; と、 m_PostProcessContext にまつわる部分を全部消した

とりあえずビルドできるようになった

ちらコード解析

BasicRenderPipeline.cs をみてみる

    public static void Render(ScriptableRenderContext context, IEnumerable<Camera> cameras)
    {
        foreach (var camera in cameras)
        {
            // Culling
            CullingParameters cullingParams;
            if (!CullResults.GetCullingParameters(camera, out cullingParams))
                continue;
            CullResults cull = CullResults.Cull(ref cullingParams, context);

            // Setup camera for rendering (sets render target, view/projection matrices and other
            // per-camera built-in shader variables).
            context.SetupCameraProperties(camera);

            // clear depth buffer
            var cmd = new CommandBuffer();
            cmd.ClearRenderTarget(true, false, Color.black);
            context.ExecuteCommandBuffer(cmd);
            cmd.Release();

            // Setup global lighting shader variables
            SetupLightShaderVariables(cull.visibleLights, context);

            // Draw opaque objects using BasicPass shader pass
            var settings = new DrawRendererSettings(cull, camera, new ShaderPassName("BasicPass"));
            settings.sorting.flags = SortFlags.CommonOpaque;
            settings.inputFilter.SetQueuesOpaque();
            context.DrawRenderers(ref settings);

            // Draw skybox
            context.DrawSkybox(camera);

            // Draw transparent objects using BasicPass shader pass
            settings.sorting.flags = SortFlags.CommonTransparent;
            settings.inputFilter.SetQueuesTransparent();
            context.DrawRenderers(ref settings);

            context.Submit();
        }
    }

なるほど、ここがRenderLoopで、ここのソースを変更する事で独自のRenderLoopを作る事が出来るようだ 中身は CommandBufferを使って 描画命令をQueueに投げている

新規プロジェクトに組み込む方法

新規プロジェクトに ScriptableRendererLoopを組み込むのは少し面倒だったのでメモする

環境

Unity 2017.1.0b8 で試した。

スクリプトの追加

サンプルのBasicRenderPipeline.csをそのまま拝借する

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;

// Very basic scriptable rendering loop example:
// - Use with BasicRenderPipelineShader.shader (the loop expects "BasicPass" pass type to exist)
// - Supports up to 8 enabled lights in the scene (directional, point or spot)
// - Does the same physically based BRDF as the Standard shader
// - No shadows
// - This loop also does not setup lightmaps, light probes, reflection probes or light cookies

[ExecuteInEditMode]
public class BasicRenderPipeline : RenderPipelineAsset
{
#if UNITY_EDITOR
    [UnityEditor.MenuItem("RenderPipeline/Create BasicRenderPipeline")]
    static void CreateBasicRenderPipeline()
    {
        var instance = ScriptableObject.CreateInstance<BasicRenderPipeline>();
        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/BasicRenderPipelineTutorial/BasicRenderPipeline.asset");
    }

#endif

    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new BasicRenderPipelineInstance();
    }
}

public class BasicRenderPipelineInstance : RenderPipeline
{
    public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        base.Render(renderContext, cameras);
        BasicRendering.Render(renderContext, cameras);
    }
}

public static class BasicRendering
{
    // Main entry point for our scriptable render loop

    public static void Render(ScriptableRenderContext context, IEnumerable<Camera> cameras)
    {
        foreach (var camera in cameras)
        {
            // Culling
            CullingParameters cullingParams;
            if (!CullResults.GetCullingParameters(camera, out cullingParams))
                continue;
            CullResults cull = CullResults.Cull(ref cullingParams, context);

            // Setup camera for rendering (sets render target, view/projection matrices and other
            // per-camera built-in shader variables).
            context.SetupCameraProperties(camera);

            // clear depth buffer
            var cmd = new CommandBuffer();
            cmd.ClearRenderTarget(true, false, Color.black);
            context.ExecuteCommandBuffer(cmd);
            cmd.Release();

            // Setup global lighting shader variables
            SetupLightShaderVariables(cull.visibleLights, context);

            // Draw opaque objects using BasicPass shader pass
            var settings = new DrawRendererSettings(cull, camera, new ShaderPassName("BasicPass"));
            settings.sorting.flags = SortFlags.CommonOpaque;
            settings.inputFilter.SetQueuesOpaque();
            context.DrawRenderers(ref settings);

            // Draw skybox
            context.DrawSkybox(camera);

            // Draw transparent objects using BasicPass shader pass
            settings.sorting.flags = SortFlags.CommonTransparent;
            settings.inputFilter.SetQueuesTransparent();
            context.DrawRenderers(ref settings);

            context.Submit();
        }
    }

    // Setup lighting variables for shader to use

    private static void SetupLightShaderVariables(VisibleLight[] lights, ScriptableRenderContext context)
    {
        // We only support up to 8 visible lights here. More complex approaches would
        // be doing some sort of per-object light setups, but here we go for simplest possible
        // approach.
        const int kMaxLights = 8;
        // Just take first 8 lights. Possible improvements: sort lights by intensity or distance
        // to the viewer, so that "most important" lights in the scene are picked, and not the 8
        // that happened to be first.
        int lightCount = Mathf.Min(lights.Length, kMaxLights);

        // Prepare light data
        Vector4[] lightColors = new Vector4[kMaxLights];
        Vector4[] lightPositions = new Vector4[kMaxLights];
        Vector4[] lightSpotDirections = new Vector4[kMaxLights];
        Vector4[] lightAtten = new Vector4[kMaxLights];
        for (var i = 0; i < lightCount; ++i)
        {
            VisibleLight light = lights[i];
            lightColors[i] = light.finalColor;
            if (light.lightType == LightType.Directional)
            {
                // light position for directional lights is: (-direction, 0)
                var dir = light.localToWorld.GetColumn(2);
                lightPositions[i] = new Vector4(-dir.x, -dir.y, -dir.z, 0);
            }
            else
            {
                // light position for point/spot lights is: (position, 1)
                var pos = light.localToWorld.GetColumn(3);
                lightPositions[i] = new Vector4(pos.x, pos.y, pos.z, 1);
            }
            // attenuation set in a way where distance attenuation can be computed:
            //  float lengthSq = dot(toLight, toLight);
            //  float atten = 1.0 / (1.0 + lengthSq * LightAtten[i].z);
            // and spot cone attenuation:
            //  float rho = max (0, dot(normalize(toLight), SpotDirection[i].xyz));
            //  float spotAtt = (rho - LightAtten[i].x) * LightAtten[i].y;
            //  spotAtt = saturate(spotAtt);
            // and the above works for all light types, i.e. spot light code works out
            // to correct math for point & directional lights as well.

            float rangeSq = light.range * light.range;

            float quadAtten = (light.lightType == LightType.Directional) ? 0.0f : 25.0f / rangeSq;

            // spot direction & attenuation
            if (light.lightType == LightType.Spot)
            {
                var dir = light.localToWorld.GetColumn(2);
                lightSpotDirections[i] = new Vector4(-dir.x, -dir.y, -dir.z, 0);

                float radAngle = Mathf.Deg2Rad * light.spotAngle;
                float cosTheta = Mathf.Cos(radAngle * 0.25f);
                float cosPhi = Mathf.Cos(radAngle * 0.5f);
                float cosDiff = cosTheta - cosPhi;
                lightAtten[i] = new Vector4(cosPhi, (cosDiff != 0.0f) ? 1.0f / cosDiff : 1.0f, quadAtten, rangeSq);
            }
            else
            {
                // non-spot light
                lightSpotDirections[i] = new Vector4(0, 0, 1, 0);
                lightAtten[i] = new Vector4(-1, 1, quadAtten, rangeSq);
            }
        }

        // ambient lighting spherical harmonics values
        const int kSHCoefficients = 7;
        Vector4[] shConstants = new Vector4[kSHCoefficients];
        SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe * RenderSettings.ambientIntensity;
        GetShaderConstantsFromNormalizedSH(ref ambientSH, shConstants);

        // setup global shader variables to contain all the data computed above
        CommandBuffer cmd = new CommandBuffer();
        cmd.SetGlobalVectorArray("globalLightColor", lightColors);
        cmd.SetGlobalVectorArray("globalLightPos", lightPositions);
        cmd.SetGlobalVectorArray("globalLightSpotDir", lightSpotDirections);
        cmd.SetGlobalVectorArray("globalLightAtten", lightAtten);
        cmd.SetGlobalVector("globalLightCount", new Vector4(lightCount, 0, 0, 0));
        cmd.SetGlobalVectorArray("globalSH", shConstants);
        context.ExecuteCommandBuffer(cmd);
        cmd.Dispose();
    }

    // Prepare L2 spherical harmonics values for efficient evaluation in a shader

    private static void GetShaderConstantsFromNormalizedSH(ref SphericalHarmonicsL2 ambientProbe, Vector4[] outCoefficients)
    {
        for (int channelIdx = 0; channelIdx < 3; ++channelIdx)
        {
            // Constant + Linear
            // In the shader we multiply the normal is not swizzled, so it's normal.xyz.
            // Swizzle the coefficients to be in { x, y, z, DC } order.
            outCoefficients[channelIdx].x = ambientProbe[channelIdx, 3];
            outCoefficients[channelIdx].y = ambientProbe[channelIdx, 1];
            outCoefficients[channelIdx].z = ambientProbe[channelIdx, 2];
            outCoefficients[channelIdx].w = ambientProbe[channelIdx, 0] - ambientProbe[channelIdx, 6];
            // Quadratic polynomials
            outCoefficients[channelIdx + 3].x = ambientProbe[channelIdx, 4];
            outCoefficients[channelIdx + 3].y = ambientProbe[channelIdx, 5];
            outCoefficients[channelIdx + 3].z = ambientProbe[channelIdx, 6] * 3.0f;
            outCoefficients[channelIdx + 3].w = ambientProbe[channelIdx, 7];
        }
        // Final quadratic polynomial
        outCoefficients[6].x = ambientProbe[0, 8];
        outCoefficients[6].y = ambientProbe[1, 8];
        outCoefficients[6].z = ambientProbe[2, 8];
        outCoefficients[6].w = 1.0f;
    }
}

RenderPipelineクラスも必要

using System;

namespace UnityEngine.Experimental.Rendering
{
    public abstract class RenderPipeline : IRenderPipeline
    {
        public virtual void Render(ScriptableRenderContext renderContext, Camera[] cameras)
        {
            if (disposed)
                throw new ObjectDisposedException(string.Format("{0} has been disposed. Do not call Render on disposed RenderLoops.", this));
        }

        public bool disposed { get; private set; }

        public virtual void Dispose()
        {
            disposed = true;
        }
    }
}

Assets/BasicRenderPipelineTutorial/BasicRenderPipeline.asset 等の名前とフォルダは矛盾が無いようにしよう

Editorメニューから CreateAssetを行うようになっている

Asset作成

Editorメニューより RenderPipeline->CreateBasicRenderPipeline (上記のコード変更してたら その名前)で Assetを作成する 成功すれば BasicRenderPipeline.asset が作成されている Scriptプロパティには 上記のスクリプト名が入っている 何か問題があれば、ファイル名やフォルダが間違えていると思われる

アタッチ

メニューより Edit->ProjectSettings->Graphic ScriptableRendererLoopSettings に先ほど作った BasicRenderPipeline アセットを指定 すると RenderPipelineをカスタマイズできる

多分 シェーダーとか、Pipelineを変えないとまともに描画できないと思うけど

右辺値参照~完全転送まで100%理解するぞ! part8 おまけ。

おまけ

可変引数テンプレートの Parameter Bagを展開しながら 全部出力する

#include<iostream>

using namespace std;



struct Concat{
    template <class... T>
    void operator ()(T&&... t) {
        concat_(std::forward<T>(t)...);
    }
private:
    void concat_() {
    }

    template <class Head, class... Tail>
    void concat_(Head&& head, Tail&&... tail)
    {
        cout << head;
        concat_(std::forward<Tail>(tail)...);
    }
    
};

auto main() -> int
{ 
  Concat con;
  con("test int:", 1 );
  
  return 1;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

上記のように template void concat(Head&& head, Tail&&... tail) とParmeterBagを 先頭1個とその他に分割し concat(std::forward(t)...); と、後半部分だけ渡して を繰り返すと全部展開できる 注意するのは ParameterBagは 0個以上なので 0個の場合もあるので、ラストは void concat_() だ。

今はcoutに出力しているが 当然 operator << を実装したくなる

#include<iostream>
#include<sstream>

using namespace std;



struct Concat{
    template <class... T>
    Concat& operator ()(T&&... t) {
        concat_(std::forward<T>(t)...);
        return *this;
    }
    
    friend ostream& operator << (ostream &os,  Concat& obj);
private:
    stringstream ss_;
    
    void concat_() {
    }

    template <class Head, class... Tail>
    void concat_(Head&& head, Tail&&... tail)
    {
        ss_ << head;
        concat_(std::forward<Tail>(tail)...);
    }    
};

    inline ostream& operator << (ostream &os,  Concat& obj){
        os << obj.ss_.str();
        obj.ss_.str("");
        return os;
    }


auto main() -> int
{ 
  Concat con;
  cout << con("test int:", 1 ) << endl;
  
  return 1;
}

operator() で thisを返し operator << にて streamへ。

最期に 再帰の部分をフリー関数へ(私の好み)

#include<iostream>
#include<sstream>

using namespace std;

ostream& concat_( ostream &ss_) {
  return ss_;
}

template <class Head, class... Tail>
    ostream& concat_(ostream &ss_, Head&& head, Tail&&... tail)
{
    ss_ << head;
    return concat_(ss_, std::forward<Tail>(tail)...);
}    


struct Concat{
    template <class... T>
    Concat& operator ()(T&&... t) {
        concat_(ss_, std::forward<T>(t)...);
        return *this;
    }
    
    friend ostream& operator << (ostream &os,  Concat& obj);
private:
    stringstream ss_;
};

    inline ostream& operator << (ostream &os,  Concat& obj){
        os << obj.ss_.str();
        obj.ss_.str("");
        return os;
    }


auto main() -> int
{ 
  Concat con;
  cout << con("test int:", 1 ) << endl;
  
  return 1;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

右辺値参照~完全転送まで100%理解するぞ! part7 可変引数テンプレート

前回の

    template<class T>
    void hoge(T&& x){
        std::forward<T>(x);
    }

universal referenceを使うことで、右辺値参照も左辺値参照もどちらも入ってくる

std::forwardを使うことで、右辺値参照の場合は ムーブコンストラクタ、左辺値参照の場合は コピーコンストラクタ が実行される

これでほぼ完ぺきだけど 完全転送と呼ぶには hoge(x,y,z...) と、可変引数に対応しなければいけません!

可変引数テンプレート

まずは可変引数テンプレートの説明

可変引数(printfのような va_list)のような書式で コンパイル時に展開される

#include<iostream>

using namespace std;

void bar( int i, string& s ){
    cout << i << s << endl;
};

template<class... T>
void hoge(T... t){
  bar(t...);
}

auto main() -> int
{ 
  hoge(1, string(" test") );
//  hoge(1);   // hoge(int) は宣言されていないので エラー

  return 1;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

上記コードの template<class... T> で、0個以上の引数をもつものにヒットする bar(t...); で、tを展開したパラメーターを引数にもつ barを呼ぶ

barに必要なだけオーバーロードすればいい

あとはこれを universal reference と std::forwardを使い

#include<iostream>

using namespace std;

void bar( int i,  string s ){
    cout << i << s << endl;
};

template<class... T>
void hoge(T&&... t){
  bar(std::forward<T>(t)...);
}

auto main() -> int
{ 
string s("s");
  hoge(1, s );
  hoge(1, " test" );
//  hoge(1);   // hoge(int) は宣言されていないので エラー

  return 1;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

これで、どんな引数でも完全転送が出来ました!

ま、ビットフィールド等 工夫しなければ完全転送できないパターンもあるようですが 一般的には これでOK

クラスのコンストラクタを可変長テンプレートにして ごにょごにょ

前回なやんだ続きではあります

関数テンプレートは暗黙的インスタンス化可能だが、クラステンプレートは不可能 ならば コンストラクタを可変長テンプレートすればいいじゃない

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

#include<iostream>
#include<sstream>

using namespace std;

struct Hoge{
void stats_(){};
stringstream ss_;

template <class Head, class... Tail>
void stats_(Head&& head, Tail&&... tail){
    ss_ << head;  
    stats_(std::forward<Tail>(tail)...);
}

template <class... T>
    Hoge (T&&... t){
        stats_(std::forward<T>(t)...);
}

friend ostream& operator << (ostream& os, const Hoge& hoge);
    
};
ostream& operator << (ostream& os, const Hoge& hoge){
    os << hoge.ss_.str();
    return os;
}


int main()
{
    cout << Hoge("aaa,", "bbb,", "ccc,") << endl;
}

つまり 関数オブジェクトは

#include<iostream>
#include<sstream>

using namespace std;

struct Hoge{
void stats_(){};
stringstream ss_;

template <class Head, class... Tail>
void stats_(Head&& head, Tail&&... tail){
//    std::cout << head;
    ss_ << head;  
    stats_(std::forward<Tail>(tail)...);
}

template <class... T>
    Hoge& operator() (T&&... t){
        stats_(std::forward<T>(t)...);
    return *this;
}

friend ostream& operator << (ostream& os, const Hoge& hoge);
    
};
ostream& operator << (ostream& os, const Hoge& hoge){
    os << hoge.ss_.str();
    return os;
}


int main()
{
    cout << Hoge()("aaa,", "bbb,", "ccc,") << endl;
}

こうやればできたんだなーー

関数オブジェクトでは 自分を返して汎用的にしてみたけど、何を返すのが正しいのかは わからない

関数テンプレートは暗黙的インスタンス化できる

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

で終わりなんだけど、少し悩んでやっぱ無理そうだったのでメモ 関数テンプレートで実装出来るのだが、使い勝手や最適化など考えて 関数オブジェクトにしたかったが 関数とクラスの テンプレートの違いで出来なかった

追記:やっぱできたぜ。メンバをテンプレートにすればOK

クラスのコンストラクタを可変長テンプレートにして ごにょごにょ - ムラサメ研究ブログ

テンプレート関数は暗黙的インスタンス化可能です!

template<T>
void hoge(T t){};


hoge(12);

可能というかむしろ、暗黙的に使うよね。

もちろん あえて明示的にも書ける

template<T>
void hoge(T t){};


hoge(12);

クラスは暗黙的インスタンス化できない

#include<iostream>


template <class T>
struct Stats{
    void operator()(T t){} const;
};

int main()
{
    Stats<int>()( 12 );
//    Stats()( 12 );   // 暗黙的インスタンス化できない
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

右辺値参照~完全転送まで100%理解するぞ! part6 Universal Referenceと std::forward

やっと UniversalReferenceですよ!

その前に復習

右辺値参照を引数にする時は あまり気にせず下記のようにするのが正しいのでしたね

#include <iostream>

using namespace std;

class X{
public:
    // explicit付けられない!
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
};

void hoge(X x){
    cout << "hoge(X)" << endl;
}

int main()
{
    X x;
    hoge(x);
    hoge(move(x));
}

呼ぶときに std::move() しなければコピーコンストラクタ、つければムーブコンストラクタが働く これは 暗黙的変換を利用し XとX&& を受け入れています

でも、コピーせず左辺値参照を渡したい事多いよね? コピーするだけ無駄だし それに STLのなかには explicit付きのものしか呼べない関数もあったりで 明示的に行いたい

そんな時は愚直に 左辺値参照と右辺値参照の関数を2つ(const参照いれて3個)実装すればよい

#include <iostream>

using namespace std;

class X{
public:
    // explicit付けられる。付けれるなら絶対付けた方がよい
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
};

void hoge(X& x){
    cout << "hoge(X&)" << endl;
}
void hoge(const X& x){
    cout << "hoge(const X&)" << endl;
}
void hoge(X&& x){
    cout << "hoge(X&&)" << endl;
}


int main()
{
    const X x;

    hoge( x );
    hoge( std::move(x) );
}

このようにすれば、当然 右辺値の時と左辺値の時で処理をわけられる

しかし、毎回同じような関数が3個。引数が増えればさらに倍増していく

それを防ぐのが Universal Referenceである

Universal Reference

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

class Hoge{
public:
    template<class T>
    void hoge(T&& x){
        // x_ = x  if lvalue
        // x_ = std::move(x) if rvale
        cout << "hoge(T&&)" << endl;
    }

private:
    X x_;
};


int main()
{
    Hoge h;
    cout << "____" << endl;
    {
        X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        h.hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        h.hoge( X() );
    }
    cout << "____" << endl;
    {
        h.hoge( std::move(X()) );
    }
    cout << "____" << endl;
}




constuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

template を使うと、 左辺値: T=X& も 右辺値: T=X&& もどちらも受け取れる

そして 関数テンプレート void hoge(T&& x) に Tが X&の時とX&&の時を考える

ここは 前回の 参照の圧縮を使えば TがX&の時は hoge(X& x) 

TがX&&の時は hoge(X&& x) となる

ところが問題はコメント部分である 引数 xが 左辺値(X&) の時は copy assignment(X& operator =(X&)) を行いたいが 右辺値(X&&)の時は move assignment(X& operator =(X&&)) を行いたい

そんな時に使えるのが std::forward である。 中身は省略し std::forwardとは、moveと同じく 単なるキャストである std::moveが 右辺値にキャストする事に対し std::forward は、Tの型にキャストを行う

実際に使ってみる

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

class Hoge{
public:
    template<class T>
    void hoge(T&& x){
        x_ = std::forward<T>(x);
        cout << "hoge(T&&)" << endl;
    }

private:
    X x_;
};


int main()
{
    Hoge h;
    cout << "____" << endl;
    {
        X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        h.hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        h.hoge( X() );
    }
    cout << "____" << endl;
    {
        h.hoge( std::move(X()) );
    }
    cout << "____" << endl;
}



constuct
____
constuct
copy assignment
hoge(T&&)
destuct
____
constuct
const copy asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

std::forwardを使えば、T&、const T&、T&& と3つの引数のオーバーロードを1関数で処理できる

とてもすばらしい。

右辺値参照~完全転送まで100%理解するぞ! part5 参照の圧縮(reference collapsing)

右辺値参照の山場の一つ Universal Referenceまであと一歩

参照の圧縮とは

UniversalReferenceそのものだが

参照は(const等のストレージクラス除き)2種類しかない

X& (左辺値参照)、X&&(右辺値参照)

X&&& 等はエラーですし X& && 等、参照を重ねる事は出来ない

でも、templateを使うと不運にも参照が重なる事がある

template<class T>
void hoge(T& t){
}

例えばこのようなテンプレートに X&型の変数を適用したら T&は X& & になり、参照が重なってしまう これらの参照が重なった時はどうなるのだろうか?

コンパイルエラーにすると、テンプレートに参照を渡すものがほとんど動かなくなる C++では当然 参照が重なった場合に明確な法則がある

#include <iostream>

using namespace std;

class X{
public:
    X(){
    }
     X(X& x){
        cout << "copy" << endl;
    }
     X(X&& x){
        cout << "move" << endl;
    }
};

int main()
{
    using Y=X&;
    using Z=X&&;

    X x;

    X( (Y&)x );
    X( (Y&&)x );
    X( (Z&)x );
    X( (Z&&)x );

}


----

copy
copy
copy
move

template 以外に typedef (using) でも参照を重ねる事が出来るので 試してみた

結果は X& & -> X&

X& && -> X&

X&& & -> X&

X&& && -> X&&

と、結論からいえば すべて右辺値参照の時のみ右辺値参照。それ以外は左辺値参照となる

このことを 参照の圧縮(reference collapsing) と呼ぶ