質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.39%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

26339閲覧

ヘッダーファイルでプロトタイプ宣言しているのに未解決の外部シンボルになる

bigbox267

総合スコア31

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

0クリップ

投稿2017/03/02 06:09

###前提・実現したいこと
読込んだ文字列を任意の区切り文字で分割してvectorに入れるという。関数を作ったのですが、モジュールを分割して作ろうと思い、プロトタイプ宣言をヘッダーファイルに関数定義をmainとは別のcppファイルに定義して使おうとしたのですが、コンパイルするとエラーになります

###発生している問題・エラーメッセージ

重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態 エラー LNK2019 未解決の外部シンボル "void __cdecl stringSplit<class std::vector<int,class std::allocator<int> > >(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &,char,class std::vector<int,class std::allocator<int> > &)" (??$stringSplit@V?$vector@HV?$allocator@H@std@@@std@@@@YAXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@DAAV?$vector@HV?$allocator@H@std@@@1@@Z) が関数 _main で参照されました。 重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態 エラー LNK1120 1 件の未解決の外部参照

###該当のソースコード

C++

1/*Source.cpp--------------------------------------*/ 2#include <iostream> 3#include <string> 4#include <vector> 5#include "Header1.hpp" 6using namespace std; 7 8int main() { 9 vector<int> vi; 10 vector<string> vs; 11 vector<double> vd; 12 string s; 13 14 while (getline(cin, s)) { 15 stringSplit(s,' ',vi); 16 } 17 18 return 0; 19} 20/*Source.cpp--------------------------------------*/ 21/*Source1.cpp-------------------------------------*/ 22#include "Header1.hpp" 23#include <string> 24#include <vector> 25 26template<class T>void stringSplit(const std::string & str, char c, T & result) { 27 stringstream ss(str); 28 string buffer; 29 if (typeid(result).name() == "class std::vector<double,class std::allocator<double> >") { 30 while (getline(ss, buffer, c)) { 31 result.push_back(stod(buffer)); 32 } 33 } 34 else if (typeid(result).name() == "class std::vector<int,class std::allocator<int> >") { 35 while (getline(ss, buffer, c)) { 36 result.push_back(stoi(buffer)); 37 } 38 } 39 else if (typeid(result).name() == "class std::vector<string,class std::allocator<string> >") { 40 while (getline(ss, buffer, c)) { 41 result.push_back(buffer); 42 } 43 } 44} 45 46/*Source1.cpp--------------------------------------*/ 47/*Header1.hpp--------------------------------------*/ 48#ifndef _HEADER1_HPP_ 49#define _HEADER1_HPP_ 50#include <string> 51 52template<class T> 53void stringSplit(const std::string & str, char c, T & result); 54 55#endif 56/*Header1.hpp--------------------------------------*/ 57

エラーとは関係ないのですが、stringSplitでの渡されたvectorの型を判断する方法でもっと簡潔に書ける方法があれば教えてほしいです

###補足情報(言語/FW/ツール等のバージョンなど)
c++
vs2015

wagashi_157👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

こんにちは。

テンプレートの場合は、ヘッダに実装(関数定義)も含めて書くことか一般的です。
cppに実装を書いて分割コンパイルする場合は、明示的な実体化が必要になります。

どういった経緯でそのように決まったのかについてテンプレートの実体化の実装方法とODR違反についてが参考になります。

エラーとは関係ないのですが、stringSplitでの渡されたvectorの型を判断する方法でもっと簡潔に書ける方法があれば教えてほしいです

その書き方は確かに問題が多いですね。

  • typeid().name()で返却される文字列は、処理系によって異なります。
  • 文字列比較になるので無駄に遅いです。

でも、それ以前にコンパイルできません。
例えば、std::vector<int>が渡された時、result.push_back(buffer);等がタイプ・ミスマッチになります。push_back()に渡せるのはintですから。

手抜きですが、下記で行けます。

C++

1template<class T>void stringSplit(const std::string & str, char c, T & result) { 2 stringstream ss(str); 3 string buffer; 4 while (getline(ss, buffer, c)) { 5 stringstream ss2(buffer); 6 typename T::value_type temp; 7 ss2 >> temp; 8 result.push_back(temp); 9 } 10}

投稿2017/03/02 07:15

編集2017/03/02 07:16
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bigbox267

2017/03/02 09:00

すみません。今回の場合の明示的実体化をするには何と書けばよいですか? Tを使っているところをvector<~~>にすればいいと思ったのですが、テンプレートではありませんと出ます あと、処理の流れについてなのですが、ループの中で変数を宣言するのはあまりよくないと自分の中では思っていたのですが、特に気にするようなことでもないですか?
Chironian

2017/03/02 09:45 編集

> 今回の場合の明示的実体化をするには何と書けばよいですか? Source1.cppの最後に例えば下記です。 template void stringSplit<vector<double> >(const std::string & str, char c, vector<double> & result); templateを忘れないこととか、関数テンプレートの定義より後に書くことが注意点です。 > ループの中で変数を宣言するのはあまりよくないと自分の中では思っていた 変数のスコープを無闇に広げるのは良くないです。グローバル変数を無闇に使うのはやめましょうというのと同じ主旨です。 ローカル変数もそれを必要とする{}ブロック内で定義した方がスコープを無闇に広げないため、より好ましいですよ。 処理性能の点では少し悩ましいですが、原則コンパイラの最適化に頼って良いと思います。 ギチギチの最適化を狙う時だけ気にする程度でよいのではないでしょうか。
bigbox267

2017/03/02 12:06

それも試してみたのですが、宣言に関数テンプレートとの互換性がありません。stringSplitの関数定義が見つかりません と出てきてしまうのです。 >変数のスコープを無闇に広げるのは良くないです。 なるほど。処理性能のことばかり気にしていて、スコープの方はあまり気にしていませんでした。
Chironian

2017/03/02 12:23

私のところでは上記文を追加してもコンパイル通ります。 上記の文を追加してエラーが出たソース・コードとそのエラーメッセージをコピペされたら、何か分かるかも知れません。
bigbox267

2017/03/02 13:03 編集

ソースコード /*Source1.cpp-------------------------------------------------------------*/ #include "Header1.hpp" #include <string> #include <vector> #include <sstream> template<class T>void stringSplit(const std::string & str, char c, T & result) { stringstream ss(str); string buffer; while (getline(ss, buffer, c)) { stringstream ss2(buffer); typename T::value_type temp; ss2 >> temp; result.push_back(temp); } } template void stringSplit<std::vector<double> >(const std::string & str, char c, std::vector<double> & result); /*Source1.cpp-------------------------------------------------------------*/
bigbox267

2017/03/02 12:46

すべてのエラー場所が18行目でした
Chironian

2017/03/02 13:02

ああっ、using namespace std;が漏れてました。 手元のソースには追加してました。私の回答漏れです。すいません。
bigbox267

2017/03/03 10:46 編集

今度は、stringSplitの関数定義が見つかりませんと出てきました。 今回はあきらめてヘッダーファイルに書くことにします。 ありがとうございました。
Chironian

2017/03/02 13:34

明示的実体化は使用するもの全て必要です。 template void stringSplit<std::vector<int> >(const std::string & str, char c, std::vector<int> & result); template void stringSplit<std::vector<string> >(const std::string & str, char c, std::vector<string> & result); 1から10まで書かなくても理解して頂けそうな印象を受けていたのでさぽってました。すいません。
bigbox267

2017/03/03 10:48 編集

本当にすみません お手数お掛けしました まだまだ、勉強不足ですね。 本当に、ありがとうございました
guest

0

正確に言うと、コンパイルではなく、リンク時のエラーです。
そして、コンパイル時点でTがわからず、シンボルが生成できない状態でリンクしようとしているために、リンク時にエラーとなっています。

templateはヘッダに記載しないと、この状態は解決できないです。

stringSplitでの渡されたvectorの型を判断

判断せず、”特殊化”を行う方がスマートかな
http://qiita.com/narumi_/items/f656678c78d50c40bc1c

投稿2017/03/02 06:46

t_obara

総合スコア5488

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bigbox267

2017/03/02 12:30

特殊化というのを初めて知りました。確かにこの方法ならきれいにすっきりとしたコードが書けますね。
guest

0

Source1.cpp をコンパイルする時点で型Tが何になるかわかんないからコンパイルできない。
ってゆーか、Tの具体的な型が決定された時点でコンパイルされる。
したがってSource1.cppをコンパイルしても(Tが決まらんから)機械語吐けない。

投稿2017/03/02 06:23

編集2017/03/02 06:48
episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bigbox267

2017/03/02 12:51

c++の仕様は難しいですね。もっと勉強します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.39%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問