LoginSignup
0
0
100万人に届けたい!私のお気に入りガジェット

ChatGPT に decltype の使い方を教えてもらった件

Last updated at Posted at 2023-07-29

希望

vector<あるタイプ> とその要素に適用する関数やラムダ式を与えたら、
vector<別のタイプ> を返す関数があったら便利だと思いませんか?

下の Apply がこの記事の成果物なんですが、こんなの C++ の仕様を隅々まで知っていてもなかなか思いつかないですよね。

どうやってこの関数に辿り着いたかというと、ChatGPT にヒントを教えてもらいました。すごいなChatGPT。

#include <iostream>
using namespace std;

template < typename T, typename F > auto
Apply( const vector< T >& _, F f ) {
	vector< decltype( f( *_.begin() ) ) > $;
	for ( auto& _: _ ) $.emplace_back( f( _ ) );
	return $;
}
int
main( int argc, char* argv[] ) {
	{	//	int to double
		vector< int >	_ = { 1, 2, 3 };
		auto coef = 1.5;
		auto $ = Apply( _, [ & ]( int _ ){ return _ * coef; } );
		for ( auto& _: $ ) cout << '\t' << _;
		cout << endl;
	}
	{	//	double to string
		vector< double >	_ = { 1.5, 2.5, 3.5 };
		auto quote = '\"';
		auto $ = Apply( _, [ & ]( double _ ){ return quote + to_string( _ ) + quote; } );
		for ( auto& _: $ ) cout << '\t' << _;
		cout << endl;
	}
	{	//	引数の配列が空
		vector< double >	_;
		auto quote = '\"';
		auto $ = Apply( _, [ & ]( double _ ){ return quote + to_string( _ ) + quote; } );
		for ( auto& _: $ ) cout << '\t' << _;
		cout << endl;
	}
}
----	実行結果
	1.5	3	4.5
	"1.500000"	"2.500000"	"3.500000"

順を追って考えてみます。

CASE 1: 関数ポインタ

例えば int の配列の全ての要素に対して、
int を引数にとって文字列を返す関数を適用して、
文字列の配列に変換したい。

#include <iostream>
using namespace std;

vector< string >
Apply( const vector< int > _, string(*f)( int ) ) {
    vector< string >    $;  
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) {
    vector< int >   _ = { 1, 2, 3 };
    auto $ = Apply( _, []( int _ ){ return '\"' + to_string( _ ) + '\"'; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	実行結果
"1"
"2"
"3"

C の時代からある、関数ポインタを引数として渡す書き方です。キャプチャなしのラムダは関数と同じ扱いでいけます。

CASE 2: キャプチャー付きのラムダ

CASE1 の f のところに関数ではなくキャプチャー付きのラムダを渡したい。

CASE1 の Apply をそのまま使うと、error: no matching function for call to 'Apply'と言われます。

これを解決する一番簡単な方法は、テンプレートを使ってキャプチャー付きのラムダを展開する方法です。

以下のようにします。

#include <iostream>
using namespace std;

template< typename F >  vector< string >
Apply( const vector< int > _, F f ) {
    vector< string >    $;  
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) {
    vector< int >   _ = { 1, 2, 3 };
    auto quote = '\"';
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	実行結果
"1"
"2"
"3"

のが一番簡単な方法です。

テンプレートを使わずに以下のようにしてもいけます。

#include <iostream>
using namespace std;

vector< string >
Apply( const vector< int > _, function< string( int ) > f ) {
    vector< string >    $;
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) {
    vector< int >   _ = { 1, 2, 3 };
    auto quote = '\"';
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	実行結果
"1"
"2"
"3"

CASE 3: 引数の一部をテンプレートに

CASE 2: の引数に int 以外の型を渡したい。

#include <iostream>
using namespace std;

template< typename T, typename F >  vector< string >
Apply( const vector< T > _, F f ) { 
    vector< string >    $;  
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) {
    vector< double >   _ = { 1, 2, 3 };
    auto quote = '\"';
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	実行結果
"1"
"2"
"3"

function は残念ながら template 引数の T を受け取ってくれないようです。

#include <iostream>
using namespace std;

template< typename T, typename F >  vector< string >
Apply( const vector< T > _, function< string( T ) > f ) { 
    vector< string >    $;  
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) {
    vector< double >   _ = { 1, 2, 3 };
    auto quote = '\"';
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	コンパイル結果
3-2.cpp:14:14: error: no matching function for call to 'Apply'
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
             ^~~~~
3-2.cpp:5:1: note: candidate template ignored: could not match 'function<std::string (T)>' (aka 'function<basic_string<char, char_traits<char>, allocator<char>> (T)>') against '(lambda at 3-2.cpp:14:24)'
Apply( const vector< T > _, function< string( T ) > f ) { 
^

CASE 4: 本題

ここからが本題です。以下のようにやればいけそうにも思えますが、実際はエラーになります。

#include <iostream>
using namespace std;


template< typename I, typename O, typename F >  vector< O > 
Apply( const vector< I > _, F f ) { 
    vector< O > $;  
    for ( auto& _: _ ) $.emplace_back( f( _ ) );
    return $;
}
int
main( int argc, char* argv[] ) { 
    vector< double >   _ = { 1, 2, 3 };
    auto quote = '\"';
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
    for ( auto& _: $ ) cout << _ << endl;
}
----	コンパイル結果
4.cpp:15:14: error: no matching function for call to 'Apply'
    auto $ = Apply( _, [ & ]( int _ ){ return quote + to_string( _ ) + quote; } );
             ^~~~~
4.cpp:6:1: note: candidate template ignored: couldn't infer template argument 'O'
Apply( const vector< I > _, F f ) {
^

テンプレート引数の O が推測できない、と言われています。途方に暮れますね。昔ならここで C++ の仕様をネットで長時間かけて調べたところです。もうそんな気力はないので、ChatGPT に聞いてみます。投げた質問を3つ順に載せておきます。

c++ でjavascript の map にあたる関数を作成することはできますか?
Javascript のように新たに生成されたvectorを戻り値として受け取るようにはできませんか?
result_of は削除されています。

この質問で生成された答えをちょっとブラッシュアップしたのが、冒頭のものです。
興味がある方はトレースしてくださいね!

0
0
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0