Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guard against type violations in non type template parameters

I usually use std::size_t where integral constants are needed in template parameters. What I notice though, is that the type system doesn't protect me from users that are happy to pass negative numbers as arguments to those parameters.

For example the following compiles giving disastrous results :

#include <iostream>

template<std::size_t I>
struct E1
{
     static void apply()
     {
         std::cout << I << std::endl;
     }
};

template<typename T>
constexpr T a = T { -1 }; 

template<std::size_t... Is>
void E2()
{
    for (auto&& i : {Is...}) std::cout << i << " "; 
    std::cout << std::endl;
}

int main()
{
    E1<-1>::apply();
    E2<-1, -2, -3>(); 
    //std::cout << a<std::size_t>; 
}

Demo

Interestingly this is not allowed for variable templates (uncommenting the last line in main causes a compilation error).

Is there any solution/workaround for the struct and function case ?

like image 604
Lorah Attkins Avatar asked Dec 24 '22 12:12

Lorah Attkins


1 Answers

This is actually a GCC bug, as there should be a diagnostic(warning or error) for narrowing here. The bug report is Narrowing conversions not caught in non-type template parameters and it has the following example:

template<        char> void   f () {}
template<unsigned int> void   g () {}


template<unsigned int> struct A {};

int main () {

  f<1024> (); // ill-formed, `char { 1024 }` is a narrowing conversion,
              // see [dcl.init.list]p7

  g<-123> (); // ill-formed, `unsigned int { -123 }` is a narrowing
              // conversion, see [dcl.init.list]p7   

  A<-123>  a; // ill-formed, see previous comment
}

and it points out the relevant quotes from the C++ standard:

[temp.arg.nontype]p1;

A template-argument for a non-type, non-template template parameeter shall be one of:

  • for a non-type template-aprameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter

    <snip />

[expr.const]p3;

A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4).


[ Note: gcc accepts testcase.cpp, while clang (correctly) issues the relevant diagnostics. ]

as sehe noted clang correctly produces a diagnostic for this with the following error:

error: non-type template argument evaluates to -1, which cannot be narrowed to type 'std::size_t' (aka 'unsigned long') [-Wc++11-narrowing]
E1<-1>::apply();
   ^

error: no matching function for call to 'E2'
E2<-1, -2, -3>(); 
^~~~~~~~~~~~~~

As a work-around you can use -Wsign-conversion -Werror(both documented here) to warn about this case and turn it into an error (see it live). I originally thought this would be caught by -Wconversion but -Wsign-conversion is turned off by default in C++.

like image 77
Shafik Yaghmour Avatar answered Jan 12 '23 00:01

Shafik Yaghmour