Rokiのチラ裏

学生による学習のログ

C++を思い出す・・・#4

このシリーズ何だか久しぶりな気がする。今回は、知ってはいるものの、なんだかぼんやりしているものをメモしてみる事にする。

特殊メンバ関数のdefault/delete宣言

クラスを定義した際に暗黙定義される特殊メンバ関数を制御するための命令。defaultは暗黙定義されるデフォルトの挙動を使用するか、inlineやvirtualなどの修飾をデフォルトの挙動に加えるかを指定するために使用される。deleteは特殊関数の暗黙定義を禁止制御する命令となる。delete命令を特殊メンバ関数に一つでも指定を加えた場合、それ以外の特殊メンバ関数も明示的に定義するか明示的にdefault命令を加えない限り、特殊メンバ関数は暗黙定義されない。

struct unique{
    unique(const unique&)=delete;
    unique& operator=(const unique&)=delete;

    unique(unique&&)=default;
    unique()=default;
    unique& operator=(unique&&)=default;
};

void f()
{
    unique a;
    unique b=std::move(a);
}

delete命令に限り、特殊メンバ関数でなくても使用する事ができるとのこと。

void f()=delete;

struct g{
    void operator()()=delete;
};

// auto h=[]()=delete{}; のような構文はない。

ラムダ式に対してdelete命令を加える構文は、用意されていない。用途的にそういう場面はないだろうが。

delegating constructors

同クラス内の他のコンストラクタに処理を移譲する事ができる機能。
元々は複数のコンストラクタで共通の処理を行う時、コンストラクタの本体で共通処理の関数を呼び出すか(パフォーマンスを阻害してしまう事が懸念される)、コンストラクタごとに全く同じ処理を書く(コードの肥大化が懸念される)記述法しかなかった。

class A;

class Param{
    friend class A;
    int data=0;
    auto get_d()const noexcept->int{return data;}
public:
    Param(int data):data(data){}
};

class A{
    int data_=0;
public:
    A(const int data):data_(data){std::cout<<"(1)"<<std::endl;}
    A(const Param& arg):A(arg.data){std::cout<<"(2)"<<std::endl;}
    auto get_d(const Param& arg)const noexcept->int{return arg.get_d();}
};

void f()
{
    constexpr int data=4;
    Param param(data);
    A a(param);
    
    std::cout<<std::boolalpha<<(a.get_d(param)==data)<<std::endl; // true
}

...ところで、仕様の確認にサンプルコードを打ってみたのだが、なんだろう、わからない。

inline namespace specification{

namespace one_{
struct if_recursive_call{
    if_recursive_call(int a,int b)
        :if_recursive_call(std::make_pair(a,b)){} // コンパイル通っちゃうよ? in gcc 6.1.0
    if_recursive_call(std::pair<int,int>&& p)
        :if_recursive_call(p.first,p.second){}
private:
    int a=0,b=0;
};

// delegating constructorsによる再帰。ill-formedである。

};

namespace two_{
template<class _Tp>
using vec_itr=typename std::vector<_Tp,std::allocator<_Tp>>::iterator;

template<class _Tp>
struct if_except{
    if_except(vec_itr<_Tp> itr) noexcept(false)
        try:a(itr){}
        catch(const std::string& e){std::cerr<<"Error 2"<<std::endl;}

    if_except(std::vector<_Tp,std::allocator<_Tp>>& v) noexcept(false)
        try:if_except(std::begin(v)){throw std::string("Error 1");}
        catch(const std::string& e){std::cerr<<e<<std::endl;}
private:
    vec_itr<_Tp> a=static_cast<vec_itr<_Tp>>(0);
};
};

// delegating constructorsから例外送出がされた場合の処理。

};

void f()
{
    using Tp=int;
    
    one_::if_recursive_call a(std::make_pair(1,2)); // Segmentation fault
    
    std::vector<Tp> v{};
    two_::if_except<Tp> b(v);
}

仕様では、delegating constructorsの再帰はill-formedのはずだが、GCC6.1.0ではコンパイルが通ってしまう...。もちろん実行するとsegmentation faultになるのだが。何か勘違いをしているのだろうか。。
また移譲先のコンストラクタから例外が送出された場合移譲先のコンストラクタのcatch節が呼び出された後に移譲元の関数tryブロックのcatch節が呼び出されるはずなのだが、呼び出されずに終了してしまう。これも何か恐らく勘違いしているんだろうなあ。ご指摘ございましたら是非よろしくお願いいたします。

Extended friend Declarations

Friend宣言の構文が拡張された。

friend elaborated-type-specifier ;
friend simple-type-specifier ;
friend typename-specifier ;

template<class _Class>
class base{
    friend _Class;
    base()=default; // private default constructor
};

class derived:public base<derived>{};

void f(){derived();}

何気にすごい便利な気がする。

Extending sizeof to apply to non-static data members without an object

まあ、表題のまんまである。

struct A{int m;};

void f()
{
    constexpr std::size_t s=sizeof(A::m),s1=sizeof(((A*)0)->m);
    constexpr std::size_t _int_s=sizeof(decltype(A::m));
    static_assert(s==_int_s&&s1==_int_s);
}

Proposed addition of __func__ predefined identifier from C99

C99互換で導入された事前定義識別子。現在いる関数名が文字列として格納されている。 識別子funcは、事前定義された関数のローカル変数として暗黙に定義される。

static const char __func__[] = "function-name";
int main()
{
    std::cout<<__func__<<std::endl;    // main
}

使い道が、あまり思いつかない。

nheriting Constructors

using宣言でBasedクラスのコンストラクタを指定することによって、Basedクラスのコンストラクタ群を暗黙に宣言させる事ができる仕様。翻訳すると、継承コンストラクタとなる。

template<class friend_class>
struct data{
    friend friend_class;
    data(const int& param0,const std::string& param1):a(param0),s(param1){}
private:
    int a=0;
    std::string s=nullptr;
};

struct X{
    X(const int& a,const std::string& b):a(a),s(b){};    
    template<class friend_class>
    X(const data<friend_class>& obj):X(obj.a,obj.s){}
private:
    int a=0;
    std::string s=nullptr;
};

struct Z:X{
    using X::X;
};

void f()
{
    data<X> obj(1,std::string("foo"));
    Z a(obj);
}

initializer list

初期化子リストである。

template<class _Tp,class Allocator=std::allocator<_Tp>>
class vec{
    const std::vector<_Tp,Allocator> data{};
public:
    vec(std::initializer_list<_Tp> init):data(std::begin(init),std::end(init)){}
};

template<class _Tp,class Allocator=std::allocator<_Tp>>
class exp_vec{
    const std::vector<_Tp,Allocator> data;
public:
    explicit exp_vec(std::initializer_list<_Tp> init)
        :data(std::begin(init),std::end(init)){}
};

void f()
{
    vec<std::size_t> v={1,2,3,4,5,};
    exp_vec<std::size_t> v1{1,2,3,4,5,};
}

initializer_listに対するautoの推論がC++11と17では変更されるとの事。GCC6.1.0ではc++1zオプションを付与しても仕様の変更は見受けられなかった。(7.0.0でも同様

% cat test.cpp
#include<initializer_list>
#include<iostream>

template<class _Tp>
void f(const std::initializer_list<_Tp>&){std::cout<<"initializer_list"<<std::endl;}
void f(const int&){std::cout<<"int"<<std::endl;}

int main()
{
    const auto x={1,2,3};
    const auto x1={1};

    f(x);   // decltype(x)==std::initializer_list<const int>
    
    f(x1);  // C++11:decltype(x1)==std::initializer_list<const int>
        // C++17:decltype(x1)==int
}
% g++ -std=c++1z test.cpp && ./a.out
initializer_list
initializer_list

Namespaces and Library Versioning

インライン名前空間の実装とそれを用いたバージョニングの例。

namespace something_lib{
    namespace v1{
        void f(){std::cout<<"v1"<<std::endl;}
    }

    inline namespace v2{
        void f(){std::cout<<"v2"<<std::endl;}
    }
}

int main()
{
    something_lib::v1::f(); // calling old version
    something_lib::v2::f(); // 明示的なコール
    something_lib::f(); // デフォルトバージョンのコール。
}

Names, Linkage, and Templates

ローカル型と無名型を、テンプレート引数として使用することが許可された内容。

template<class _Tp>
int to_int(_Tp x){return static_cast<int>(x);}

int main()
{
    enum{E};

    to_int(E);
    to_int<decltype(E)>(E);
}

overrideとfinal

overrideはvirtual member functionのoverrideを明示的に宣言し、finalは派生クラスのvirtual member functionのoverrideを制約するキーワードである。

class A{
    virtual void func_final() final;
    virtual void func();
    void func_no_virtual();
};

class X:public A{
    // void func_final(); ill-formed. func_finalはfinalキーワードが付与されている
    void func() override;
    // void func(short a) override; ill-formed 当てはまるbase classのmember functionはない
    // void func_no_virtual() override; ill-formed.base classのfunc_no_virtualはvirtual functionでない
};

class B final{
    virtual void func();
};

/*class Y:public B{
    void func(); ill-formed.baseクラスはfinalキーワードが付与されておりoverrideできない
};*/

Extending Move Semantics To *this

メンバ関数に対してアンパサンド、またはダブルアンパサンドを付与する事で∗thisがlvalueである場合に呼び出されるメンバ関数オーバーロードと∗thisがrvalueである場合に呼び出されるメンバ関数オーバーロードを定義する事ができる仕様。

template<class _Tp,int N=0>
class X final{
    std::array<_Tp,N> ar;
public:
    X()=default;

    const std::array<_Tp,N>& array()const&{return ar;}
    std::array<_Tp,N> array() &&{return std::move(ar);}

    // auto array()const& -> const std::array<_Tp,N>&{return ar;}
    // auto array() && -> std::array<_Tp,N>{return std::move(ar);}
};

void f()
{
    constexpr int length=5;
    using type=int;

    X<type,length> obj;

    const std::array<type,length>& ar=obj.array();
    std::array<type,length> ar1=X<type,length>().array();
}