翻訳単位と名前空間と`using`

※初歩的な勘違いをしていたのでメモ

次のコードは動かない

といってもリンクができないだけで分割コンパイルはできる(できてしまう)

#pragma once
namespace Hoge{

typedef float hoge;
hoge setHoge();
void getHoge(hoge value);

};
#include <iostream>
#include "hoge.hpp"
using namespace std;
using namespace Hoge;

hoge setHoge(){
    return ((hoge)10);
}
void getHoge(hoge value){
    cout << value << endl;
}
#include <iostream>
#include "hoge.hpp"
using namespace Hoge;

int main(){
    hoge x = setHoge(); // x = 10
    getHoge(x);  // print 10
    return 0;
}
make:
    clang++ -std=c++20 -c hoge.cpp main.cpp
    nm hoge.o > hoge.sym
    nm main.o > main.sym
    clang++ hoge.o main.o -o test
clang++ -std=c++20 -c hoge.cpp main.cpp
nm hoge.o > hoge.sym
nm main.o > main.sym
clang++ hoge.o main.o -o test
Undefined symbols for architecture x86_64:
  "Hoge::getHoge(float)", referenced from:
      _main in main.o
  "Hoge::setHoge()", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [make] Error 1

なぜか?

上のスクリプトで分割コンパイルすると,それぞれ#include "hoge.hpp"が展開された上で翻訳単位としてhogemainの2つが発生して,それぞれが処理されてシンボルテーブルを持つオブジェクトとして出てくる main側はHoge周りの実装をしていないため,hoge.hppの宣言した内容に期待して,名前空間Hogeを含めたシンボルをあらかじめ設定する ゆえにhoge側は名前空間Hogeを含めたシンボルを実体に持つような実装が期待されており,そうでなければリンク時に名前を解決できず失敗する

ソース側でusing namespace Hogeを利用して(名前空間の解決の記法を省略して)実装することは不可能であり,それはコンパイラ名前空間の探索と適切な配置よりも先に勝手に大域的な配置をしてしまうためである… なんでそうなのかは知りませんが,歴史的な経緯とかあるのでしょうか?やっぱり外部リンケージがデフォルトな言語だからなんですかね,externconstも難解すぎてこのままではstaticおじさんになってしまう…

hoge

nm hoge.o | grep Hoge

0000000000000010 T __Z7getHogef
0000000000000000 T __Z7setHogev

main

nm main.o | grep Hoge

                 U __ZN4Hoge7getHogeEf
                 U __ZN4Hoge7setHogeEv

解決策

namespace Hoge{}の中に実装を書く

多分これが一番楽だと思います,ただしクソデカネストが許容できる場合の話,俺は辛い耐えられない

#include <iostream>
#include "hoge.hpp"
using namespace std;
using namespace Hoge;
namespace Hoge{

hoge setHoge(){
    return ((hoge)10);
}
void getHoge(hoge value){
    cout << value << endl;
}

}

実装の名前に必ずHoge::をつける

using namespace Hoge;しつつクソデカネストを使わないなら,多分これが一番楽だと思います また宣言されたtypedefclassの名前スコープを無視できる,人によっては折衷案に見える?

#include <iostream>
#include "hoge.hpp"
using namespace std;
using namespace Hoge;

hoge Hoge::setHoge(){
    return ((hoge)10);
}
void Hoge::getHoge(hoge value){
    cout << value << endl;
}