float値をテンプレートパラメータとして使用できないのはなぜですか?


120

floatテンプレートパラメータとして使用しようとすると、コンパイラはこのコードを要求しますが、int正常に動作します。

floatテンプレートパラメーターとして使用できないからでしょうか?

#include<iostream>
using namespace std;

template <class T, T defaultValue>
class GenericClass
{
private:
    T value;
public:
    GenericClass()
    {
        value = defaultValue;
    }

    T returnVal()
    {
        return value;
    }
}; 


int main()
{
    GenericClass <int, 10> gcInteger;
    GenericClass < float, 4.6f> gcFlaot;

    cout << "\n sum of integer is "<<gcInteger.returnVal();
    cout << "\n sum of float is "<<gcFlaot.returnVal();

    return 0;       
}

エラー:

main.cpp: In function `int main()':
main.cpp:25: error: `float' is not a valid type for a template constant parameter
main.cpp:25: error: invalid type in declaration before ';' token

main.cpp:28: error: request for member `returnVal' in `gcFlaot',
                    which is of non-class type `int'

著者はRon Pentonによる「ゲームプログラマー向けのデータ構造」を読んでいますが、作成者はを渡しますが、float試してもコンパイルされないようです。


1
作者は本当に非タイプテンプレートパラメータfloatとして使用しますか?それはどの章にありますか?
K-ballo 2013年

1
それを見つけた、それは「テンプレートパラメータとしての値の使用」にあります...
K-ballo

回答:


37

現在のC ++標準ではfloatテンプレート(型以外のパラメーター)として(つまり、実数)または文字列リテラルを使用できません。もちろん、通常の引数としてfloatおよびchar *タイプを使用できます。

おそらく、作者は現在の標準に準拠していないコンパイラを使用していますか?


8
標準からの関連セクションへのリンクまたはコピーを提供してください
thecoshman '17

2
@thecoshman標準の関連セクション+詳細については、(新しく投稿された)回答を参照してください。
FilipRoséen-refp '17 / 07/17

1
C ++ 11では、文字列リテラルをテンプレートの非型パラメーターとして使用することがほぼ可能です。テンプレートが文字パックを取る場合template<char ...cs>、文字列リテラルはコンパイル時にそのようなパックに変換できます。こちらがideoneのデモです。(デモはC ++ 14ですが、C ++ 11に戻すのは簡単です- std::integer_sequence唯一の難しさです)
アーロン・マクデイド

char &*別の場所でリテラルを定義する場合、テンプレートパラメータとして使用できることに注意してください。回避策としてかなりうまくいきます。
StenSoft 2015

137

シンプルな答え

標準では、浮動小数点を型のないテンプレート引数として許可していません。これは、C ++ 11標準の次のセクションで参照できます。

14.3.2 / 1テンプレートのタイプではない引数[temp.arg.nontype]

非タイプ、非テンプレートテンプレートパラメータのテンプレート引数は、次のいずれかになります。

  • 整数型または列挙型の非タイプテンプレートパラメータの場合、テンプレートパラメータのタイプの変換された定数式(5.19)。

  • タイプではないテンプレートパラメータの名前。または

  • 静的ストレージ期間と外部または内部リンケージのあるオブジェクトのアドレス、または外部または内部リンケージのある関数のアドレスを指定する定数式(5.19)。 (括弧)&&id-expressionとして。ただし、名前が関数または配列を参照する場合は&を省略でき、対応するtemplate-parameterが参照の場合は&を省略します。または

  • nullポインター値(4.10)に評価される定数式。または

  • nullメンバーポインター値(4.11)に評価される定数式。または

  • 5.3.1で説明されているように表現されたメンバーへのポインタ。


しかし..しかし..なぜ!?

これはおそらく、浮動小数点計算が正確に表現できないためです。これが許可された場合、このようなことをすると、エラー/奇妙な動作が発生する可能性があります。

func<1/3.f> (); 
func<2/6.f> ();

同じ関数を2回呼び出すことを意図しましたが、2つの計算の浮動小数点表現が完全に同じであることが保証されていないため、これは当てはまらない場合があります。


浮動小数点値をテンプレート引数としてどのように表現しますか?

ではC++11、あなたはかなり高度な書くことができ、定表現constexprの浮動値のコンパイル時の分子/分母を計算し、その後、別の整数引数としてこれらの2を渡します)。

互いに近い浮動小数点値が同じ分子/分母を生成するように、ある種のしきい値を定義することを忘れないでください。それ以外の場合は、浮動小数点値を非型として許可しない理由として前述の同じ結果を生成するため、それは少し無意味です。 テンプレート引数


56
C ++ 11ソリューションは<ratio>、§20.10で「コンパイル時の有理演算」として記述されています。これはあなたの例に正解です。
Potatoswatter

1
@Potatoswatter Afaik STLには、フロートを分子/分母に変換するメソッドはありませんか<ratio>
FilipRoséen-refp

3
これは本当に説得力のある説明を与えるものではありません。全体のポイント浮動小数点のは、それが正確な値を表しているということです。持っている数値を他のものの近似値として自由に扱うことができ、そうすることはしばしば有用ですが、数値自体は正確です。
tmyklebu 2015年

4
@FilipRoséen-refp:すべての浮動小数点数は正確です。浮動小数点演算は、私が知っているすべてのターゲットで明確に定義されています。ほとんどの浮動小数点演算では、浮動小数点の結果が生成されます。コンパイラ実装者にターゲットの奇妙な浮動小数点演算を実装させたくない委員会に感謝することができますが、「演算が整数演算と異なる」ことが浮動小数点テンプレート引数を禁止する正当な理由であるとは思いません。結局のところ、これは任意の制限です。
tmyklebu '10 / 10/15

5
@iheanyi:標準は何12345 * 12345と言っていますか?(signed intの幅やその式がUBであるかどうかを指定していなくても、テンプレートパラメーター許可されintます。)
tmyklebu

34

これが制限である理由の1つを提供するためだけです(少なくとも現在の標準では)。

テンプレートの特殊化に一致する場合、コンパイラーは型引数以外の引数を含むテンプレート引数を照合します。

その性質上、浮動小数点値は正確ではなく、その実装はC ++標準では指定されていません。その結果、2つの浮動小数点非型引数が実際に一致する時期を判断することは困難です。

template <float f> void foo () ;

void bar () {
    foo< (1.0/3.0) > ();
    foo< (7.0/21.0) > ();
}

これらの式は必ずしも同じ「ビットパターン」を生成するわけではないため、同じ特殊化を使用したことを保証することはできません。


16
これは、フロートを完全に言語から禁止することに対するほとんどの議論です。または、少なくとも、==演算子を禁止します:-)実行時にこの不正確さをすでに受け入れていますが、コンパイル時もそうではありませんか?
Aaron McDaid 2015

3
@AaronMcDaidに同意します。これはあまり議論の余地はありません。したがって、定義には注意が必要です。だから何?定数から取得したもので機能する限り、それはすでにかなりの改善です。
einpoklum 2016年

1
C ++ 20では、非型テンプレートパラメーターとしてfloat(他のオブジェクト型)を使用できるようになりました。それでもC ++ 20はfloatの実装を指定していません。これは、アインポクルムとアーロンにポイントがあることを示しています。
Andreas H.

20

実際、フロートリテラルをテンプレートパラメータとして使用することはできません。標準のセクション14.1を 参照してください(「非タイプテンプレートパラメーターは、次の(オプションでcv修飾された)タイプのいずれかでなければなりません...」)

floatへの参照をテンプレートパラメータとして使用できます。

template <class T, T const &defaultValue>
class GenericClass

.
.

float const c_four_point_six = 4.6; // at global scope

.
.

GenericClass < float, c_four_point_six> gcFlaot;

11
あなたはできる。同じことはしません。参照をコンパイル時の定数として使用することはできません。

12

パラメータをconstexprsとして独自のクラスにラップします。フロートのセットでクラスをパラメーター化するので、これは事実上トレイトに似ています。

class MyParameters{
    public:
        static constexpr float Kd =1.0f;
        static constexpr float Ki =1.0f;
        static constexpr float Kp =1.0f;
};

そして、クラスタイプをパラメータとして取るテンプレートを作成します

  template <typename NUM, typename TUNING_PARAMS >
  class PidController {

      // define short hand constants for the PID tuning parameters
      static constexpr NUM Kp = TUNING_PARAMS::Kp;
      static constexpr NUM Ki = TUNING_PARAMS::Ki;
      static constexpr NUM Kd = TUNING_PARAMS::Kd;

      .... code to actually do something ...
};

そしてそれをそのように使います...

int main (){
    PidController<float, MyParameters> controller;
    ...
    ...
}

これにより、コンパイラーは、同じパラメーター・パックを使用して、テンプレートのインスタンス化ごとにコードの単一のインスタンスのみが作成されることを保証できます。これですべての問題が回避され、テンプレートクラス内でconstexprとしてfloatおよびdoubleを使用できます。


5

タイプごとにデフォルトを固定してもよい場合は、タイプを作成して定数として定義し、必要に応じて特殊化することができます。

template <typename T> struct MyTypeDefault { static const T value; };
template <typename T> const T MyTypeDefault<T>::value = T();
template <> struct MyTypeDefault<double> { static const double value; };
const double MyTypeDefault<double>::value = 1.0;

template <typename T>
class MyType {
  public:
    MyType() { value = MyTypeDefault<T>::value; }
  private:
    T value;
 };

C ++ 11を使用している場合は、デフォルト値を定義するときにconstexprを使用できます。C ++ 14では、MyTypeDefaultを、構文的に少しすっきりしたテンプレート変数にすることができます。

//C++14
template <typename T> constexpr T MyTypeDefault = T();
template <> constexpr double MyTypeDefault<double> = 1.0;

template <typename T>
class MyType {
  private:
    T value = MyTypeDefault<T>;
 };

2

他の答えは、おそらく浮動小数点テンプレートのパラメーターを望まない理由を示しますが、実際のブレーカーIMOは、「==」を使用した同等性とビットごとの同等性が同じではないということです。

  1. -0.0 == 0.0しかし、0.0-0.0等しいビット単位ではありません

  2. NAN != NAN

どちらの種類の等価性も、型の等価性の良い候補にはなりません。もちろん、ポイント2は、==型の等価性の判断に使用を無効にします。代わりにビット単位の等価性を使用することもできますが、それx != yはそれを意味せずMyClass<x>MyClass<y>異なる型(2による)であり、かなり奇妙です。


1

あなたはいつでもそれを偽造することができます...

#include <iostream>

template <int NUM, int DEN>
struct Float
{
    static constexpr float value() { return (float)NUM / (float)DEN; }
    static constexpr float VALUE = value();
};

template <class GRAD, class CONST>
struct LinearFunc
{
    static float func(float x) { return GRAD::VALUE*x + CONST::VALUE; }
};


int main()
{
    // Y = 0.333 x + 0.2
    // x=2, y=0.866
    std::cout << " func(2) = "
              << LinearFunc<Float<1,3>, Float<1,5> > ::func(2) << std::endl;
}

参照:http : //code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html


3
A float!=有理数。2つは非常に別々のアイデアです。1つは仮数と指数を使用して計算され、もう1つは有理数ですfloat。有理数で表現できるすべての値がで表現できるわけではありません。
リチャードJ.ロスIII

2
@ RichardJ.RossIII A floatは間違いなく有理数floatですが、2つintのsの比率として表現できないs があります。仮数は整数であり、2 ^指数は整数である
Caleth

1

doubleをコンパイル時の定数にする必要がない場合は、ポインターとして渡すことができます。

#include <iostream>

extern const double kMyDouble = 0.1;;

template <const double* MyDouble>
void writeDouble() {
   std::cout << *MyDouble << std::endl; 
}

int main()
{
    writeDouble<&kMyDouble>();
   return 0;
}

参照はおそらくより良いです、@ moonshadowの回答を
einpoklum

1
これは実際にコンパイル時に適切に削減されますか?
Ant6n 2016年

1

C ++ 20以降、これは可能です。

これにより、元の質問に対する回答も得られます。

Why can't I use float value as a template parameter?

まだ誰も標準に実装していないからです。基本的な理由はありません。

C ++ 20では、型のないテンプレートパラメータを浮動小数点数やクラスオブジェクトにすることもできます。

クラスオブジェクトにはいくつかの要件があり(リテラルタイプである必要があります)、ユーザー定義の演算子==(詳細)などの病理学的なケースを除外するために他のいくつかの要件を満たします。

私たちも使用できます auto

template <auto Val>
struct Test {
};

struct A {};
static A aval;
Test<aval>  ta;
Test<A{}>  ta2;
Test<1.234>  tf;
Test<1U>  ti;

GCC 9(および10)はクラスの非タイプテンプレートパラメータを実装していますが、フロートはまだ実装されていないことに注意してください。


0

固定精度のみを表現したい場合は、このような手法を使用して、floatパラメーターをintに変換できます。

たとえば、成長係数が1.75の配列は、2桁の精度(100で除算)を想定して次のように作成できます。

template <typename _Kind_, int _Factor_=175>
class Array
{
public:
    static const float Factor;
    _Kind_ * Data;
    int Size;

    // ...

    void Resize()
    {
         _Kind_ * data = new _Kind_[(Size*Factor)+1];

         // ...
    }
}

template<typename _Kind_, int _Factor_>
const float Array<_kind_,_Factor_>::Factor = _Factor_/100;

テンプレート引数リストで1.75を175として表現したくない場合は、常にいくつかのマクロでラップできます。

#define FloatToIntPrecision(f,p) (f*(10^p))

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...

それ...::Factor = _Factor_/100.0;以外の場合は整数除算になります。
alfC 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.