希望
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 は削除されています。
この質問で生成された答えをちょっとブラッシュアップしたのが、冒頭のものです。
興味がある方はトレースしてくださいね!