0

I have a templated buffer class with a simple printing function.

template <typename valueType, typename sumType, int N>
class IM_buffer
{
public:
    IM_buffer()
        : bufferValues(), numValues(-1), currentSum() { }

    void record(valueType sample)
    {
        // Set buffer index to be recorded
        numValues++;

        // Get memory location for oldest element in buffer
        valueType& oldest = bufferValues[modIdx(numValues)];

        // Add the input value to the current sum and take away the value being replaced
        currentSum += sample - oldest;

        // And now do the actual replacement in the same memory location
        oldest = sample;
    }

    valueType   getCurrent()            { return bufferValues[modIdx(numValues)];           }
    valueType   getNthPrev(int Nprev)   { return bufferValues[modIdx(numValues-Nprev)];     }
    sumType     getCurrentSum()         { return currentSum;                                }
    double      getAvg()                { return (double) currentSum / MIN(numValues+1, N); }
    int         getNumValues()          { return numValues+1;                               }
    int         getBufferSize()         { return N;                                         }

    void printBuff()
    {
        for (int ii=0; ii<N; ii++)
        {
            // if it's an integer type I need:
            printf("bufferValues[%2d]=%4d\n",ii,bufferValues[ii]);
            // but if it's a floating point type I need:
            printf("bufferValues[%2d]=%8g\n",ii,bufferValues[ii]);
        }
    }

    void clear()
    {
        for (int ii=0; ii<N; ii++)
            bufferValues[ii] = (valueType) 0.0;
        numValues = 0;
        currentSum = (sumType) 0.0;
    }

private:
    valueType bufferValues[N];
    int numValues;
    sumType currentSum;

    int modIdx(int a) { return (a % N + N) % N; }
};

However, the format string of the printf should depend on what the data type is (e.g. int, vs. float, vs double). I've seen discussions like this but I don't really want to print out the data type, I just need to change the printf format string based on the data type. Can anyone point me in the right direction for how to implement some condition logic to select the right printf?

Mike
  • 65
  • 6
  • 1
    That's one of the nice things about C++ streams like `std::cout`, it can handle different types without fixed-format specifiers like for `printf`. In short, don't use `printf` in C++. – Some programmer dude Nov 16 '17 at 13:34
  • @Someprogrammerdude I can't remember the exact conflict, but `#include ` broke some other aspect of the code so that's why I was using `printf` - a smaller set of include headers. – Mike Nov 16 '17 at 13:44
  • Then you are doing something you should not do. You should ask about that problem in a new question. – Some programmer dude Nov 16 '17 at 13:45
  • And using `printf` is going to be even worse if you use a non-builtin type as the template argument. What happens if you try to use `std::string` or perhaps your own custom class? With `std::cout` there is an overloaded `<<` function for `std::string` already, and you can easily create one for custom classes and types. You can't do that with `printf` really. – Some programmer dude Nov 16 '17 at 13:47
  • Unfortunately the bulk of the code is not mine to be changing so I'm not sure that I can get around not being able to use streams in this particular case. Luckily, I will not be dealing with any non-builtin types for the problem at hand. – Mike Nov 16 '17 at 13:50
  • There is a solution, one that I don't really recommend since there are nicer ways to do it in C++, and it is to use function overloading. You can have a private static member function that takes an `int` as argument and prints using `"%d"`. Then one overload for `double` which uses `"%g"`, etc. What I really recommend is that you fix the underlying problem that you can include `` (are you doing `using namespace std;`? [Perhaps that's the the problem](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice), because of name clashes?) – Some programmer dude Nov 16 '17 at 13:52
  • I was thinking about function overloading - thanks for the suggestion. – Mike Nov 16 '17 at 14:03
  • Concerning the `` problem, I am not doing `using namespace std;`. I actually just tried doing the `cout` approach instead and it worked. I guess whoever had broken things related to `` unbroke them and didn't tell me. Sorry for leading things in circles. If you post the original suggestion as an answer I'll mark it accepted. – Mike Nov 16 '17 at 14:03

3 Answers3

1

As others mentioned in the comments, you should use std::cout that has overloads for all built in types. However, if you really insist on using printf, you can try the following hack:

#include <typeinfo>  // for typeid

std::string printfCmd("I wish to print this: ");
// int myVar = 69;  // uncomment to change var type to int
char myVar = 'd';
if (typeid(int) == typeid(myVar)) {
    printfCmd += "%d";
} else if (typeid(char) == typeid(myVar)) {
    printfCmd += "%c";
} else {
    // some warning/error
}

printf(printfCmd.c_str(), myVar);

It is not a good solution, use it only if you really have to.

pptaszni
  • 5,591
  • 5
  • 27
  • 43
0

If you don't want to use the C++ overloaded insertion operators, you can write your own overloaded function:

void print(int i) {
    printf("%4d",i);
}

void print(double d) {
    printf("%8g",d);
}

etc.

Now you can call print from your printBuff function when needed:

printf("bufferValues["%2d] =",ii);
print(bufferValues[ii]);
printf("\n");
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
0

Another option, which may or may not be better than the other solutions depending on your situation, is to use templates to template the format specifiers at compile-time based on type.

TL;DR example programs (linked below):

Anyways:

template <typename T> static const char *outFormat = ""; // pick a default.
template <> const char *outFormat<float> = "%8.4f";
template <> const char *outFormat<double> = "%8.4lf";
template <> const char *outFormat<long double> = "%8.4Lf";
template <> const char *outFormat<char> = "%c";

And use them like this, for example:

typedef float some_api_type; // some external libraries unknown type

void printAPIValue (some_api_type value) {
    printf(outFormat<some_api_type>, value);
    printf("\n");
}

// or, for example, printing an stl container...
template <typename Container> void printValues (const Container &c) {
    using value_type = typename Container::value_type;
    printf("[ ");
    for (auto i = c.cbegin(); i != c.cend(); ++ i) {
        printf(outFormat<value_type>, *i);
        printf(" ");
    }
    printf("]\n");
}

So, e.g. this program:

some_api_type value = 4;
std::vector<float> floats = { 1.0f, 1.1f, -2.345f };
std::vector<char> chars = { 'h', 'e', 'l', 'l', 'o' };

printAPIValue(value);
printValues(floats);
printValues(chars);

Would produce this output:

  4.0000
[   1.0000   1.1000  -2.3450 ]
[ h e l l o ]

There are many various forms of the above, so you'd have to do something that works well in your situation. For example:

  • I specified the % and field width / precision for convenience. You could remove all that and construct it as needed, in which case using an std::string might save you some typing later. This program:

      template <typename T> static string outFormat;
      template <> const string outFormat<float> = "f";
      template <> const string outFormat<int> = "d";
    
      // graceful handling of unsupported types omitted.
      template <typename Container> void printValues (const Container &c) {
          using value_type = typename Container::value_type;
          int index = 0;
          printf("list with format %s:\n", outFormat<value_type>.c_str());
          for (auto i = c.cbegin(); i != c.cend(); ++ i, ++ index) {
              string format = "item[%d] = %10" + outFormat<value_type> + "\n";
              printf(format.c_str(), index, *i);
          }
      }
    
      int main () {
          std::vector<float> floats = { 1.0f, 1.1f, -2.345f };
          std::vector<int> ints = { -1, 1, -2, 9 };
          printValues(floats);
          printValues(ints);
      }
    

    Outputs:

      list with format f:
      item[0] =   1.000000
      item[1] =   1.100000
      item[2] =  -2.345000
      list with format d:
      item[0] =         -1
      item[1] =          1
      item[2] =         -2
      item[3] =          9
    
  • You can also make use of cinttypes (C's inttypes.h) for some platform-independent stuff, e.g. this program:

      #include <cstdio>
      #include <cinttypes> // types and PRI* sized format specifiers
    
      template <typename T> static const char *hexFormat = "<unsupported type>";
      template <> const char *hexFormat<uint8_t> = "0x%02" PRIx8;
      template <> const char *hexFormat<uint16_t> = "0x%04" PRIx16;
      template <> const char *hexFormat<uint32_t> = "0x%08" PRIx32;
      template <> const char *hexFormat<uint64_t> = "0x%016" PRIx64;
    
      template <typename UIntValue> void printHex (UIntValue value) {
          printf(hexFormat<UIntValue>, value);
          printf("\n");
      }
    
      int main () {
          printHex((size_t)0xABCD1234);
          printHex(9U);
          printHex(9UL);
          printHex((unsigned char)'!');
          printHex(9.0);
      }
    

    On a 64-bit platform, that might output:

      0x00000000abcd1234
      0x00000009
      0x0000000000000009
      0x21
      <unsupported type>
    
  • You have various options to enforce that supported types are used, I'm not going to list them, use your imagination. But; since you're using templates, you can get some compile-time checks if you want. For example, this program, using static_assert:

      #include <cstdio>
    
      template <typename T> static constexpr const char *format = nullptr;
      template <> constexpr const char * format<short> = "%hd\n";
      template <> constexpr const char * format<int> = "%d\n";
      template <> constexpr const char * format<long> = "%ld\n";
    
      template <typename Value> void print (Value value) {
          static_assert(format<Value> != nullptr, "Unsupported type.");
          printf(format<Value>, value);
      }
    
      int main () {
          print<short>(1);
          print<int>(1);
          print<float>(1);  // <-- compilation will fail here
          print<long>(1);
      }
    

    Will fail to compile that print<float> line with:

      error: static assertion failed: Unsupported type.
    

    Which can help you catch errors.


There's probably a lot more to say about all of this but I'm sick of typing and need to get back to work. Hopefully this is helpful to somebody.

Jason C
  • 38,729
  • 14
  • 126
  • 182